Zwróć wyniki

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

ResultEventBus udostępnia 2 modele komunikacji:

  • Wyniki oparte na zdarzeniach: w przypadku zdarzeń tymczasowych i jednorazowych (takich jak wyświetlenie paska powiadomień z potwierdzeniem lub wywołanie efektu ubocznego) użyj ResultEffect.
  • Wyniki oparte na stanie: do obserwowania najnowszego wyniku jako Compose State przy użyciu conflateAsState.

Konfigurowanie magistrali zdarzeń wynikowych

Aby udostępnić ResultEventBus w miejscach docelowych funkcji kompozycyjnych, dodaj rememberResultEventBusNavEntryDecorator do listy dekoratorów przekazywanych do NavDisplay. Dzięki temu treści każdego miejsca docelowego mają kompozycję lokalną LocalResultEventBus.

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 zwracane są typowe typy (np. String, Boolean lub elementy podstawowe) lub gdy wiele miejsc docelowych zwraca różne instancje tego samego typu danych.
  • Klucze pochodzące od typu: jeśli nie określisz klucza, ResultEventBus automatycznie wygeneruje klucz, używając reprezentacji toString typu wyniku KClass (np. Contact::class.toString()). Kluczy pochodzących od typu używaj w przypadku różnych typów danych związanych z domeną.

Zwracanie wyników z miejsca docelowego

Aby zachować możliwość wielokrotnego użycia i testowania funkcji kompozycyjnych ekranu, nie uzyskuj dostępu do LocalResultEventBus bezpośrednio w interfejsie ekranu. Zamiast tego udostępnij funkcje lambda wywołania zwrotnego z ekranu. W obiekcie entryProvider obsłuż wywołanie zwrotne, wysyłając wynik za pomocą LocalResultEventBus.current i wracając do poprzedniego ekranu.

Wyniki możesz wysyłać 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 pochodzącego od 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 obiektów obserwowalnych opartych na stanie.

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

Obsługa zdarzeń jednorazowych za pomocą ResultEffect

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

ResultEffect utrzymuje kolejkę wyników przychodzących. Jeśli dla danego klucza zostanie wysłanych kilka wyników, ResultEffect przetworzy każdy z nich w kolejności, w jakiej zostały wysłane. Dodatkowo ResultEffect działa w zakresie współprogramu, co pozwala wywoływać funkcje zawieszające bezpośrednio w treści efektu.

Możesz nasłuchiwać wyników powiązanych z konkretnym 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, używając klucza pochodzącego od 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 przechodzenia między miejscami docelowymi ResultEffect wykonuje tę sekwencję cyklu życia:

  1. Nadawca emituje: miejsce docelowe nadawcy wysyła wynik za pomocą resultBus.sendResult(resultKey = "pickup_address", address) i wyciąga stos wsteczny.
  2. Odbiorca wchodzi do kompozycji: miejsce docelowe odbiorcy staje się aktywnym ekranem, a ResultEffect zaczyna nasłuchiwać wyników.
  3. Odbiorca przetwarza wyniki: ResultEffect odbiera 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 jest usuwane ze stosu wstecznego, ResultEffect opuszcza kompozycję i przestaje nasłuchiwać wyników.

Obserwuj najnowsze wyniki jako stan za pomocą conflateAsState

Jeśli potrzebujesz tylko najnowszej wartości wyniku, aby bezpośrednio zmodyfikować lub przefiltrować stan lokalnego interfejsu, i chcesz, aby Compose automatycznie rekomponował interfejs przy każdej aktualizacji wyniku, wywołaj conflateAsState na ResultEventBus.

Możesz obserwować wyniki powiązane z konkretnym 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łasny ResultEventBus wewnętrznie za pomocą rememberResultEventBus.

Możesz jawnie utworzyć i wyciągnąć ResultEventBus, gdy musisz:

  • Przekaż instancję ResultEventBus bezpośrednio do komponentów, których nie można łączyć, lub do grafów wstrzykiwania zależności.
  • wysyłać lub obserwować wyniki z głównego szkieletu aplikacji (np. paska aplikacji lub panelu nawigacji) poza hierarchią miejsc docelowych.

Aby przenieść ResultEventBus, utwórz go za pomocą rememberResultEventBus i przekaż 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 usuwanie

Gdy miejsce docelowe wykorzysta wynik jednorazowy, usuń go z magistrali zdarzeń za pomocą metody removeResult. Zapobiega to ponownemu dostarczaniu przez magistralę wcześniejszych zdarzeń do nowych obserwatorów, gdy miejsca docelowe ponownie wejdą 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")
    }
}

Wyniki możesz wyczyścić według klucza jawnego (resultBus.removeResult(resultKey)) lub klucza pochodnego (resultBus.removeResult<T>()). Szczegółowe informacje o dopasowywaniu kluczy znajdziesz w artykule Klucze wyników.

Przepisy

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