Restituisci risultati

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 State utilizzando conflateAsState.

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 esempio String, Boolean o primitive) o quando più destinazioni restituiscono istanze diverse dello stesso tipo di dati.
  • Chiavi derivate dal tipo: se non specifichi una chiave esplicita, ResultEventBus genera automaticamente una chiave utilizzando la rappresentazione toString del KClass del tipo di risultato (ad esempio Contact::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:

  1. Il mittente genera: la destinazione mittente invia un risultato utilizzando resultBus.sendResult(resultKey = "pickup_address", address) e apre il back stack.
  2. Il ricevitore entra nella composizione: la destinazione del ricevitore diventa la schermata attiva e ResultEffect inizia ad ascoltare i risultati.
  3. Il ricevitore elabora i risultati: ResultEffect riceve ed esegue il corpo dell'effetto per ogni risultato inviato per quella chiave, elaborando tutte le emissioni nell'ordine in cui sono state inviate.
  4. Il ricevitore esce dalla composizione: quando la destinazione del ricevitore viene rimossa dal back stack, ResultEffect esce 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 ResultEventBus direttamente 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: