A partire dalla versione 1.2.0 di Navigation 3, puoi restituire i risultati delle destinazioni utilizzando l'API ResultEventBus.
ResultEventBus fornisce due modelli di comunicazione:
- Risultati basati sugli eventi: per eventi temporanei e una tantum (come la visualizzazione di una snackbar di conferma o l'attivazione di un effetto collaterale) utilizzando
ResultEffect. - Risultati basati sullo stato: per osservare l'ultimo risultato come Compose
StateutilizzandoconflateAsState.
Configura il bus di eventi dei risultati
Per rendere ResultEventBus disponibile per le tue destinazioni componibili, aggiungi
rememberResultEventBusNavEntryDecorator all'elenco
di decoratori passati al tuo NavDisplay. In questo modo, i contenuti di ogni destinazione hanno una composizione locale LocalResultEventBus.
NavDisplay( /* ... */ entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberResultEventBusNavEntryDecorator() ) )
Chiavi dei risultati
ResultEventBus identifica e indirizza ogni risultato utilizzando una chiave. I mittenti e i destinatari abbinano i risultati utilizzando la stessa chiave.
Puoi specificare le chiavi dei risultati in due modi:
- Chiavi esplicite: puoi specificare una chiave esplicita (ad esempio
resultKey = "pickup_address"). Utilizza le chiavi esplicite quando restituisci tipi comuni (ad esempioString,Booleano primitive) o quando più destinazioni restituiscono istanze diverse dello stesso tipo di dati. - Chiavi derivate dal tipo: se non specifichi una chiave esplicita,
ResultEventBusgenera automaticamente una chiave utilizzando la rappresentazionetoStringdelKClassdel tipo di risultato (ad esempioContact::class.toString()). Utilizza le chiavi derivate dal tipo per tipi di dati distinti e specifici del dominio.
Restituisci i risultati di una destinazione
Per mantenere i componibili dello schermo riutilizzabili e testabili, non accedere a LocalResultEventBus direttamente all'interno della UI dello schermo. Devi invece esporre le lambda di callback dallo schermo. In entryProvider, gestisci il callback inviando il risultato utilizzando LocalResultEventBus.current e tornando indietro.
Puoi inviare i risultati utilizzando una chiave dei risultati esplicita:
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() } ) }
Puoi anche inviare i risultati utilizzando una chiave derivata dal tipo:
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() } ) }
Ricevi i risultati
Le destinazioni possono utilizzare i risultati tramite effetti basati sugli eventi o osservabili basati sullo stato.
| API | Comportamento | Casi d'uso consigliati |
|---|---|---|
ResultEffect |
In coda: elabora in ordine tutti i risultati emessi per la chiave. | Eventi una tantum ed effetti collaterali (ad esempio la visualizzazione di una snackbar o l'inoltro a un ViewModel). |
conflateAsState |
Conflated: ignora i risultati intermedi e conserva solo l'ultimo risultato come State di Compose. |
Modificatori di stato della UI leggeri (come tag di filtro attivi o override di selezione). |
Gestisci gli eventi unici con ResultEffect
Utilizza ResultEffect quando gestisci eventi una tantum, ad esempio l'attivazione di analisi, la visualizzazione di una snackbar o l'inoltro di un risultato a un ViewModel.
ResultEffect mantiene una coda per i risultati in arrivo. Se vengono inviati più risultati per una determinata chiave, ResultEffect elabora ogni risultato nell'ordine in cui è stato inviato. Inoltre, ResultEffect viene eseguito in un ambito di coroutine, il che ti consente di chiamare le funzioni di sospensione direttamente all'interno del corpo dell'effetto.
Puoi ascoltare i risultati associati a una chiave di risultato esplicita:
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") } ) }
Puoi anche ascoltare i risultati utilizzando una chiave derivata dal tipo:
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 ) }
Quando si naviga tra le destinazioni, ResultEffect viene eseguito tramite la seguente sequenza del ciclo di vita:
- Il mittente genera: la destinazione mittente invia un risultato utilizzando
resultBus.sendResult(resultKey = "pickup_address", address)e apre il back stack. - Il ricevitore entra nella composizione: la destinazione del ricevitore diventa la schermata attiva e
ResultEffectinizia ad ascoltare i risultati. - Il ricevitore elabora i risultati:
ResultEffectriceve ed esegue il corpo dell'effetto per ogni risultato inviato per quella chiave, elaborando tutte le emissioni nell'ordine in cui sono state inviate. - Il ricevitore esce dalla composizione: quando la destinazione del ricevitore viene rimossa dal back stack,
ResultEffectesce dalla composizione e smette di ascoltare i risultati.
Osserva i risultati più recenti come stato con conflateAsState
Quando hai bisogno solo dell'ultimo valore del risultato per modificare o filtrare direttamente lo stato della UI locale e vuoi che Compose esegua automaticamente la ricomposizione ogni volta che il risultato viene aggiornato, chiama conflateAsState su ResultEventBus.
Puoi osservare i risultati associati a una chiave di risultato esplicita:
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") } ) }
Puoi anche osservare i risultati utilizzando una chiave derivata dal tipo:
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 ) }
Hoist ResultEventBus
Per impostazione predefinita, rememberResultEventBusNavEntryDecorator crea e memorizza
il proprio ResultEventBus internamente utilizzando
rememberResultEventBus.
Puoi creare e spostare esplicitamente un ResultEventBus quando devi:
- Passa l'istanza
ResultEventBusdirettamente ai componenti non componibili o ai grafici di dependency injection. - Inviare o osservare i risultati dall'impalcatura dell'app di primo livello (ad esempio una barra dell'app o un riquadro di navigazione a scomparsa) al di fuori della gerarchia delle destinazioni.
Per eseguire l'hoisting di ResultEventBus, crealo utilizzando rememberResultEventBus e passalo a 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 ) )
Gestisci e cancella i risultati
Quando una destinazione utilizza un risultato una tantum, cancellalo dal bus di eventi utilizzando
removeResult. In questo modo, il bus non invia nuovamente gli eventi passati ai nuovi osservatori quando le destinazioni rientrano nella composizione:
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") } }
Puoi cancellare i risultati in base alla chiave esplicita (resultBus.removeResult(resultKey)) o alla chiave derivata dal tipo (resultBus.removeResult<T>()). Per informazioni dettagliate sulla corrispondenza delle chiavi, consulta Chiavi dei risultati.
Ricette
Per esempi di codice eseguibile completo che mostrano diverse strategie di passaggio dei risultati, consulta le seguenti ricette:
- Ricetta dei risultati basati sugli eventi
- Ricetta per i risultati basati sullo stato
- Ricetta per risultati serializzabili