Zwróć wyniki

Od wersji 3 nawigacji 1.2.0 możesz zwracać wyniki z miejsc docelowych za pomocą interfejsu ResultEventBus API.

ResultEventBus udostępnia 2 modele komunikacji:

  • Wyniki oparte na zdarzeniach: w przypadku przejściowych, jednorazowych zdarzeń (np. wyświetlania paska powiadomień z potwierdzeniem lub wywoływania efektu ubocznego) używaj ResultEffect.
  • Wyniki oparte na stanie: do obserwowania najnowszego wyniku w Compose State za pomocą conflateAsState.

Konfigurowanie magistrali zdarzeń wyników

Aby udostępnić ResultEventBus w miejscach docelowych z funkcją kompozycji, dodaj rememberResultEventBusNavEntryDecorator do listy dekoratorów przekazywanych do NavDisplay. Dzięki temu treści każdego miejsca docelowego będą miały LocalResultEventBus lokalizację kompozycji.

NavDisplay(
    /* ... */
    entryDecorators = listOf(
        rememberSaveableStateHolderNavEntryDecorator(),
        rememberResultEventBusNavEntryDecorator()
    )
)

Klucze wyników

ResultEventBus identyfikuje i kieruje każdy wynik za pomocą klucza. Nadawcy i odbiorcy dopasowują wyniki za pomocą tego samego klucza.

Klucze wyników możesz określić na 2 sposoby:

  • Klucze jawne: możesz określić klucz jawny (np. resultKey = "pickup_address"). Używaj kluczy jawnych, gdy zwracasz typy wspólne (np. String, Boolean lub typy proste) albo gdy wiele miejsc docelowych zwraca różne instancje tego samego typu danych.
  • Klucze pochodne typu: jeśli nie określisz jawnego klucza, ResultEventBus automatycznie wygeneruje klucz na podstawie toString reprezentacji KClass typu wyniku (np. Contact::class.toString()). Używaj kluczy pochodnych typu w przypadku odrębnych typów danych specyficznych dla domeny.

Zwracanie wyników z miejsca docelowego

Aby zachować możliwość ponownego użycia i testowania komponentów ekranu, nie uzyskuj dostępu do LocalResultEventBus bezpośrednio w interfejsie ekranu. Zamiast tego udostępniaj wywołania zwrotne lambd z ekranu. W funkcji entryProvider obsłuż wywołanie zwrotne, wysyłając wynik za pomocą funkcji LocalResultEventBus.current i wracając.

Możesz wysyłać wyniki za pomocą jawnego klucza wyniku:

import androidx.compose.runtime.Composable
import androidx.navigation3.runtime.result.LocalResultEventBus

entry<AddressPickerRoute> {
    val resultBus = LocalResultEventBus.current

    AddressPickerScreen(
        onAddressSelected = { selectedAddress: Address ->
            resultBus.sendResult(
                resultKey = "pickup_address",
                result = selectedAddress
            )
            navigator.goBack()
        }
    )
}

Możesz też wysyłać wyniki za pomocą klucza pochodnego typu:

import androidx.compose.runtime.Composable
import androidx.navigation3.runtime.result.LocalResultEventBus

entry<ContactPickerRoute> {
    val resultBus = LocalResultEventBus.current

    ContactPickerScreen(
        onContactSelected = { selectedContact: Contact ->
            resultBus.sendResult(result = selectedContact)
            navigator.goBack()
        }
    )
}

Otrzymywanie wyników

Miejsca docelowe mogą wykorzystywać wyniki za pomocą efektów opartych na zdarzeniach lub obserwowalnych stanów.

Interfejs API Zachowanie Zalecane przypadki użycia
ResultEffect W kolejce: przetwarza wszystkie wyniki wygenerowane dla klucza w kolejności. jednorazowe zdarzenia i efekty uboczne (np. wyświetlanie paska powiadomień lub przekierowywanie do ViewModel);
conflateAsState Połączone: odrzuca wyniki pośrednie i zachowuje tylko najnowszy wynik jako Compose State. proste modyfikatory stanu interfejsu (np. aktywne tagi filtrów lub zastąpienia wyboru);

Obsługa jednorazowych wydarzeń za pomocą ResultEffect

Używaj ResultEffect do obsługi zdarzeń jednorazowych, takich jak wywoływanie Analytics, wyświetlanie paska powiadomień lub przekazywanie wyniku do ViewModel.

ResultEffect utrzymuje kolejkę wyników przychodzących. Jeśli dla danego klucza zostanie wysłanych wiele wyników, usługa ResultEffect przetworzy je w kolejności, w jakiej zostały wysłane. Dodatkowo ResultEffect działa w zakresie współprogramu, co umożliwia wywoływanie funkcji zawieszających bezpośrednio w treści efektu.

Możesz nasłuchiwać wyników powiązanych z kluczem wyniku:

import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation3.runtime.result.ResultEffect

@Composable
fun RideSummaryScreen(
    onOpenAddressPicker: (key: String) -> Unit,
    viewModel: RideSummaryViewModel = viewModel()
) {
    ResultEffect<Address>(resultKey = "pickup_address") { address ->
        viewModel.onPickupAddressSelected(address)
    }

    ResultEffect<Address>(resultKey = "destination_address") { address ->
        viewModel.onDestinationAddressSelected(address)
    }

    RideSummaryContent(
        pickupAddress = viewModel.pickupAddress,
        destinationAddress = viewModel.destinationAddress,
        onPickPickup = { onOpenAddressPicker("pickup_address") },
        onPickDestination = { onOpenAddressPicker("destination_address") }
    )
}

Możesz też nasłuchiwać wyników za pomocą klucza pochodzącego z typu:

import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation3.runtime.result.ResultEffect

@Composable
fun ComposeMessageScreen(
    onPickContact: () -> Unit,
    snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
    viewModel: ComposeMessageViewModel = viewModel()
) {
    ResultEffect<Contact> { contact ->
        // Suspending calls are supported directly in the effect body
        snackbarHostState.showSnackbar("Selected ${contact.name}")
        viewModel.onRecipientSelected(contact)
    }

    ComposeMessageContent(
        recipient = viewModel.recipient,
        onPickContact = onPickContact
    )
}

Podczas nawigacji między miejscami docelowymi funkcja ResultEffect jest wykonywana w następującej kolejności:

  1. Nadawca wysyła: miejsce docelowe nadawcy wysyła wynik za pomocą resultBus.sendResult(resultKey = "pickup_address", address) i usuwa z powrotem stos wsteczny.
  2. Odbiornik wprowadza kompozycję: miejsce docelowe odbiornika staje się aktywnym ekranem, a ResultEffect zaczyna nasłuchiwać wyników.
  3. Odbiorca przetwarza wyniki: ResultEffect otrzymuje i wykonuje treść efektu dla każdego wyniku wysłanego dla tego klucza, przetwarzając wszystkie emisje w kolejności, w jakiej zostały wysłane.
  4. Odbiornik opuszcza kompozycję: gdy miejsce docelowe odbiornika zostanie usunięte ze stosu wstecznego, ResultEffect opuści kompozycję i przestanie nasłuchiwać wyników.

Obserwuj najnowsze wyniki jako stan z conflateAsState

Jeśli potrzebujesz tylko najnowszej wartości wyniku, aby bezpośrednio modyfikować lub filtrować lokalny stan interfejsu, i chcesz, aby Compose automatycznie ponownie komponował interfejs po każdej aktualizacji wyniku, wywołaj conflateAsState na ResultEventBus.

Możesz obserwować wyniki powiązane z kluczem wyniku:

import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.navigation3.runtime.result.LocalResultEventBus

@Composable
fun ThemePreviewScreen(
    onOpenColorPicker: (key: String) -> Unit
) {
    val resultBus = LocalResultEventBus.current

    val primaryColor by resultBus.conflateAsState<Color>(
        resultKey = "primary_color",
        defaultValue = MaterialTheme.colorScheme.primary
    )

    val accentColor by resultBus.conflateAsState<Color>(
        resultKey = "accent_color",
        defaultValue = MaterialTheme.colorScheme.tertiary
    )

    ThemePreviewContent(
        primaryColor = primaryColor,
        accentColor = accentColor,
        onPickPrimary = { onOpenColorPicker("primary_color") },
        onPickAccent = { onOpenColorPicker("accent_color") }
    )
}

Możesz też obserwować wyniki, używając klucza pochodzącego od typu:

import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.navigation3.runtime.result.LocalResultEventBus

@Composable
fun FilterableProductListScreen(
    initialFilter: ProductFilter = ProductFilter.All,
    onOpenFilterPicker: () -> Unit
) {
    val resultBus = LocalResultEventBus.current

    // Observe latest filter result as Compose State, starting with initialFilter
    val activeFilter by resultBus.conflateAsState<ProductFilter>(
        defaultValue = initialFilter
    )

    ProductListContent(
        activeFilter = activeFilter,
        onOpenFilterPicker = onOpenFilterPicker
    )
}

Podnośnik ResultEventBus

Domyślnie rememberResultEventBusNavEntryDecorator tworzy i zapamiętuje własne ResultEventBus wewnętrznie za pomocą rememberResultEventBus.

W razie potrzeby możesz jawnie utworzyć i podnieść ResultEventBus:

  • Przekazywanie instancji ResultEventBus bezpośrednio do komponentów, których nie można łączyć, lub wykresów wstrzykiwania zależności.
  • Wysyłanie lub obserwowanie wyników z ram aplikacji najwyższego poziomu (np. paska aplikacji lub panelu nawigacyjnego) poza hierarchią miejsca docelowego.

Aby podnieść ResultEventBus, utwórz go za pomocą rememberResultEventBus i przekaż go do rememberResultEventBusNavEntryDecorator(resultEventBus):

import androidx.compose.runtime.Composable
import androidx.navigation3.runtime.result.rememberResultEventBus
import androidx.navigation3.runtime.result.rememberResultEventBusNavEntryDecorator
import androidx.navigation3.ui.NavDisplay

// Hoist the ResultEventBus at the top level
val resultEventBus = rememberResultEventBus()

// Pass the hoisted bus to the decorator
val resultEventBusNavEntryDecorator =
    rememberResultEventBusNavEntryDecorator<NavKey>(
        resultEventBus = resultEventBus
    )

NavDisplay(
    /* ... */
    entryDecorators = listOf(
        rememberSaveableStateHolderNavEntryDecorator(),
        resultEventBusNavEntryDecorator
    )
)

Zarządzanie wynikami i ich czyszczenie

Gdy miejsce docelowe wykorzysta jednorazowy wynik, usuń go z magistrali zdarzeń za pomocą funkcji removeResult. Zapobiega to ponownemu dostarczaniu przez magistralę poprzednich zdarzeń do nowych obserwatorów, gdy miejsca docelowe ponownie wchodzą w skład kompozycji:

import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation3.runtime.result.LocalResultEventBus
import androidx.navigation3.runtime.result.ResultEffect

@Composable
fun NotificationSettingsScreen(
    viewModel: NotificationViewModel = viewModel()
) {
    val resultBus = LocalResultEventBus.current

    ResultEffect<ConfirmationResult>(resultKey = "confirm_permission") { confirmation ->
        viewModel.onPermissionConfirmed(confirmation)

        // Clear the result after consumption to prevent re-delivery
        resultBus.removeResult(resultKey = "confirm_permission")
    }
}

Możesz wyczyścić wyniki według klucza jawnego (resultBus.removeResult(resultKey)) lub klucza pochodnego typu (resultBus.removeResult<T>()). Więcej informacji o dopasowywaniu kluczy znajdziesz w sekcji Klucze wyników.

Przepisy

Pełne przykłady kodu, które można uruchomić i które pokazują różne strategie przekazywania wyników, znajdziesz w tych przepisach: