Afficher les résultats

À 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 State Compose à l'aide de conflateAsState.

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 que String, Boolean ou 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, ResultEventBus génère automatiquement une clé à l'aide de la représentation toString du KClass du 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 :

  1. 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".
  2. Le récepteur entre dans la composition : la destination du récepteur devient l'écran actif et ResultEffect commence à écouter les résultats.
  3. Le récepteur traite les résultats : ResultEffect reç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.
  4. Le récepteur quitte la composition : lorsque la destination du récepteur est retirée de la pile "Retour", ResultEffect quitte 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 ResultEventBus directement 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 :