À partir de Navigation 3 1.2.0, vous pouvez renvoyer les résultats des destinations à l'aide de l'API ResultEventBus.
ResultEventBus propose deux modèles de communication :
- Résultats basés sur des événements : pour les événements ponctuels et transitoires (comme l'affichage d'une snackbar de confirmation ou le déclenchement d'un effet secondaire), utilisez
ResultEffect. - Résultats basés sur l'état : pour observer le dernier résultat en tant que
StateCompose à l'aide deconflateAsState.
Configurer le bus d'événements de résultat
Pour rendre ResultEventBus disponible pour vos destinations composables, ajoutez rememberResultEventBusNavEntryDecorator à la liste des décorateurs transmis à votre NavDisplay. Cela fournit au contenu de chaque destination une composition locale LocalResultEventBus.
NavDisplay( /* ... */ entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberResultEventBusNavEntryDecorator() ) )
Clés de résultat
ResultEventBus identifie et achemine chaque résultat à l'aide d'une clé. Les expéditeurs et les destinataires font correspondre les résultats en utilisant la même clé.
Vous pouvez spécifier des clés de résultat de deux manières :
- Clés explicites : vous pouvez spécifier une clé explicite (telle que
resultKey = "pickup_address"). Utilisez des clés explicites lorsque vous renvoyez des types courants (tels queString,Booleanou des primitives), ou lorsque plusieurs destinations renvoient différentes instances du même type de données. - Clés dérivées du type : lorsque vous ne spécifiez pas de clé explicite,
ResultEventBusgénère automatiquement une clé à l'aide de la représentationtoStringduKClassdu type de résultat (par exemple,Contact::class.toString()). Utilisez des clés dérivées du type pour les types de données distincts et spécifiques au domaine.
Renvoyer des résultats depuis une destination
Pour que les composables d'écran restent réutilisables et testables, n'accédez pas à LocalResultEventBus directement dans l'UI de votre écran. À la place, exposez les lambdas de rappel depuis votre écran. Dans votre entryProvider, gérez le rappel en envoyant le résultat à l'aide de LocalResultEventBus.current et en revenant en arrière.
Vous pouvez envoyer des résultats à l'aide d'une clé de résultat explicite :
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() } ) }
Vous pouvez également envoyer des résultats à l'aide d'une clé dérivée d'un type :
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() } ) }
Recevoir les résultats
Les destinations peuvent consommer des résultats à l'aide d'effets basés sur les événements ou d'observables basés sur l'état.
| API | Comportement | Cas d'utilisation recommandés |
|---|---|---|
ResultEffect |
En file d'attente : traite tous les résultats émis pour la clé dans l'ordre. | Événements ponctuels et effets secondaires (comme l'affichage d'un snackbar ou le transfert vers un ViewModel). |
conflateAsState |
Conflated : supprime les résultats intermédiaires et ne conserve que le dernier résultat en tant que State Compose. |
Modificateurs d'état de l'UI légers (tels que les tags de filtre actifs ou les sélections forcées). |
Gérer les événements ponctuels avec ResultEffect
Utilisez ResultEffect pour gérer les événements ponctuels tels que le déclenchement d'analyses, l'affichage d'une snackbar ou le transfert d'un résultat vers un ViewModel.
ResultEffect gère une file d'attente pour les résultats entrants. Si plusieurs résultats sont envoyés pour une clé donnée, ResultEffect traite chaque résultat dans l'ordre dans lequel il a été envoyé. De plus, ResultEffect s'exécute dans une portée de coroutine, ce qui vous permet d'appeler des fonctions de suspension directement dans le corps de l'effet.
Vous pouvez écouter les résultats associés à une clé de résultat explicite :
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") } ) }
Vous pouvez également écouter les résultats à l'aide d'une clé dérivée du type :
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 ) }
Lorsque vous naviguez entre des destinations, ResultEffect s'exécute selon la séquence de cycle de vie suivante :
- L'expéditeur émet : la destination de l'expéditeur envoie un résultat à l'aide de
resultBus.sendResult(resultKey = "pickup_address", address)et supprime la pile "Retour". - Le récepteur entre dans la composition : la destination du récepteur devient l'écran actif et
ResultEffectcommence à écouter les résultats. - Le récepteur traite les résultats :
ResultEffectreçoit et exécute le corps de l'effet pour chaque résultat envoyé pour cette clé, en traitant toutes les émissions dans l'ordre dans lequel elles ont été envoyées. - Le récepteur quitte la composition : lorsque la destination du récepteur est retirée de la pile "Retour",
ResultEffectquitte la composition et cesse d'écouter les résultats.
Observer les derniers résultats en tant qu'état avec conflateAsState
Lorsque vous n'avez besoin que de la dernière valeur de résultat pour modifier ou filtrer directement l'état de l'UI locale et que vous souhaitez que Compose recompose automatiquement chaque fois que le résultat est mis à jour, appelez conflateAsState sur ResultEventBus.
Vous pouvez observer les résultats associés à une clé de résultat explicite :
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") } ) }
Vous pouvez également observer les résultats à l'aide d'une clé dérivée du type :
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 ) }
Monte-charge ResultEventBus
Par défaut, rememberResultEventBusNavEntryDecorator crée et mémorise son propre ResultEventBus en interne à l'aide de rememberResultEventBus.
Vous pouvez créer et hisser explicitement un ResultEventBus lorsque vous avez besoin de :
- Transmettez l'instance
ResultEventBusdirectement aux composants non composables ou aux graphiques d'injection de dépendances. - Envoyez ou observez les résultats de la structure de l'application de premier niveau (tels qu'une barre d'application ou un tiroir de navigation) en dehors de la hiérarchie de destination.
Pour hisser ResultEventBus, créez-le à l'aide de rememberResultEventBus et transmettez-le à 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 ) )
Gérer et effacer les résultats
Lorsqu'une destination utilise un résultat unique, effacez-le du bus d'événements à l'aide de removeResult. Cela empêche le bus de retransmettre les événements passés aux nouveaux observateurs lorsque les destinations reviennent dans la composition :
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") } }
Vous pouvez effacer les résultats par clé explicite (resultBus.removeResult(resultKey)) ou par clé dérivée du type (resultBus.removeResult<T>()). Pour en savoir plus sur la correspondance des clés, consultez Clés de résultat.
Recettes
Pour obtenir des exemples de code exécutables complets illustrant différentes stratégies de transmission des résultats, consultez les recettes suivantes :
- Recette de résultats basée sur les événements
- Recette de résultat basée sur l'état
- Recette de résultat sérialisable