Restituisci risultati

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

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 (come String, Boolean o primitive) o quando più destinazioni restituiscono istanze diverse dello stesso tipo di dati.
  • Chiavi derivate dal tipo: quando 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.

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:

  1. Il mittente emette: la destinazione del mittente invia un risultato utilizzando resultBus.sendResult(resultKey = "pickup_address", address) e rimuove lo stack precedente.
  2. Il ricevitore inserisce la composizione: la destinazione del ricevitore diventa lo schermo attivo e ResultEffect inizia ad ascoltare i risultati.
  3. Il destinatario 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 abbandona la composizione: quando la destinazione del ricevitore viene estratta dallo stack precedente, ResultEffect abbandona 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 ResultEventBus direttamente 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: