À partir de Navigation 3 1.2.0, vous pouvez renvoyer des résultats à partir de 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 temporaires (comme l'affichage d'un snackbar de confirmation ou le déclenchement d'un effet secondaire) à l'aide de
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 que ResultEventBus soit disponible pour vos destinations composables, ajoutez rememberResultEventBusNavEntryDecorator à la liste des décorateurs transmis à votre NavDisplay. Cela fournit au contenu de chaque destination un composant local 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 à l'aide de 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 des types de données distincts et spécifiques au domaine.
Renvoyer les résultats d'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. Exposez plutôt des lambdas de rappel à partir de 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 les 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 du 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 les résultats à l'aide d'effets basés sur des é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 d'UI légers (tels que les tags de filtre actifs ou les remplacements de sélection). |
Gérer les événements ponctuels avec ResultEffect
Utilisez ResultEffect pour gérer des é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 un champ d'application 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 ) }
Lors de la navigation entre les destinations, ResultEffect s'exécute via 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 affiche 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 son corps d'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 se 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 devez :
- Transmettez l'instance
ResultEventBusdirectement aux composants non composables ou aux graphiques d'injection de dépendances. - Envoyez ou observez les résultats de l'échafaudage d'application de niveau supérieur (comme une barre d'application ou un panneau 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 renvoyer les événements passés aux nouveaux observateurs lorsque les destinations sont recomposées :
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 de résultats, consultez les recettes suivantes :
- Recette de résultat basée sur un événement
- Recette de résultat basée sur l'état
- Recette de résultat sérialisable