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
Stateza 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,Booleanlub 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,
ResultEventBusautomatycznie wygeneruje klucz na podstawietoStringreprezentacjiKClasstypu 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:
- Nadawca wysyła: miejsce docelowe nadawcy wysyła wynik za pomocą
resultBus.sendResult(resultKey = "pickup_address", address)i usuwa z powrotem stos wsteczny. - Odbiornik wprowadza kompozycję: miejsce docelowe odbiornika staje się aktywnym ekranem, a
ResultEffectzaczyna nasłuchiwać wyników. - Odbiorca przetwarza wyniki:
ResultEffectotrzymuje 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 zostanie usunięte ze stosu wstecznego,
ResultEffectopuś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
ResultEventBusbezpoś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:
- Przepis na wynik oparty na zdarzeniach
- Przepis na wynik oparty na stanie
- Przepis na wynik z możliwością serializacji