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
Stateprzy użyciuconflateAsState.
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,Booleanlub 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,
ResultEventBusautomatycznie wygeneruje klucz, używając reprezentacjitoStringtypu wynikuKClass(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:
- Nadawca emituje: miejsce docelowe nadawcy wysyła wynik za pomocą
resultBus.sendResult(resultKey = "pickup_address", address)i wyciąga stos wsteczny. - Odbiorca wchodzi do kompozycji: miejsce docelowe odbiorcy staje się aktywnym ekranem, a
ResultEffectzaczyna nasłuchiwać wyników. - Odbiorca przetwarza wyniki:
ResultEffectodbiera 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. - Odbiornik opuszcza kompozycję: gdy miejsce docelowe odbiornika jest usuwane ze stosu wstecznego,
ResultEffectopuszcza 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ę
ResultEventBusbezpoś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:
- Przepis na wyniki na podstawie zdarzeń
- Przepis na wynik na podstawie stanu
- Przepis na wynik możliwy do serializacji