A partire dalla versione 3 1.2.0 di Navigation, 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 (ad esempio 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 durante la composizione
StateutilizzandoconflateAsState.
Configurare il bus di eventi dei risultati
Per rendere ResultEventBus disponibile per le destinazioni componibili, aggiungi
rememberResultEventBusNavEntryDecorator all'elenco
dei decoratori passati a NavDisplay. In questo modo, i contenuti di ogni destinazione avranno 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 (comeString,Booleano primitive) o quando più destinazioni restituiscono istanze diverse dello stesso tipo di dati. - Chiavi derivate dal tipo: quando 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.
Restituire risultati da una destinazione
Per mantenere i composable dello schermo riutilizzabili e testabili, non accedere a
LocalResultEventBus direttamente all'interno della UI dello schermo. Mostra invece le espressioni lambda di callback
dalla schermata. In entryProvider, gestisci il callback inviando il risultato utilizzando LocalResultEventBus.current e tornando indietro.
Puoi inviare i risultati utilizzando una chiave di risultato 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() } ) }
Ricevere i risultati
Le destinazioni possono utilizzare i risultati utilizzando effetti basati sugli eventi o osservabili basati sullo stato.
| API | Comportamento | Casi d'uso consigliati |
|---|---|---|
ResultEffect |
In coda: elabora tutti i risultati emessi per la chiave in ordine. | Eventi e effetti collaterali una tantum (ad esempio la visualizzazione di una snackbar o l'inoltro a un ViewModel). |
conflateAsState |
Unito: elimina i risultati intermedi e conserva solo l'ultimo risultato come State. |
Modificatori dello stato della UI leggeri (ad esempio tag di filtri attivi o override della selezione). |
Gestire eventi unici con ResultEffect
Utilizza ResultEffect quando gestisci eventi una tantum come
l'attivazione di Analytics, 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 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 secondo la seguente sequenza del ciclo di vita:
- Il mittente emette: la destinazione del mittente invia un risultato utilizzando
resultBus.sendResult(resultKey = "pickup_address", address)e rimuove lo stack precedente. - Il ricevitore inserisce la composizione: la destinazione del ricevitore diventa lo
schermo attivo e
ResultEffectinizia ad ascoltare i risultati. - Il destinatario 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 abbandona la composizione: quando la destinazione del ricevitore viene estratta
dallo stack precedente,
ResultEffectabbandona la 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 dell'interfaccia utente locale e vuoi che Compose si ricomponga automaticamente 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 ) }
Paranco ResultEventBus
Per impostazione predefinita, rememberResultEventBusNavEntryDecorator crea e memorizza
il proprio ResultEventBus internamente utilizzando
rememberResultEventBus.
Puoi creare e alzare esplicitamente un ResultEventBus quando necessario:
- Passa l'istanza
ResultEventBusdirettamente a componenti non componibili o grafici di inserimento delle dipendenze. - Invia o osserva i risultati della struttura di primo livello dell'app (ad esempio una barra dell'app o un riquadro di navigazione a scomparsa) al di fuori della gerarchia di destinazione.
Per sollevare 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 ) )
Gestire e cancellare i risultati
Quando una destinazione utilizza un risultato una tantum, cancellalo dal bus di eventi utilizzando
removeResult. In questo modo, l'autobus non invia di nuovo
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, vedi Chiavi dei risultati.
Ricette
Per esempi di codice eseguibili completi che mostrano diverse strategie di passaggio dei risultati, consulta le seguenti ricette:
- Ricetta dei risultati basata sugli eventi
- Ricetta basata sullo stato del risultato
- Ricetta per risultati serializzabili