Mostrar resultados

A partir de Navigation 3 1.2.0, puedes devolver resultados de destinos con la API de ResultEventBus.

ResultEventBus proporciona dos modelos de comunicación:

  • Resultados basados en eventos: Para eventos transitorios y únicos (como mostrar una barra de notificaciones de confirmación o activar un efecto secundario) con ResultEffect.
  • Resultados basados en el estado: Para observar el resultado más reciente a medida que Compose State con conflateAsState.

Configura el bus de eventos de resultados

Para que ResultEventBus esté disponible para tus destinos de elementos de componibilidad, agrega rememberResultEventBusNavEntryDecorator a la lista de decoradores que se pasan a tu NavDisplay. Esto proporciona el contenido de cada destino con un elemento local de composición LocalResultEventBus.

NavDisplay(
    /* ... */
    entryDecorators = listOf(
        rememberSaveableStateHolderNavEntryDecorator(),
        rememberResultEventBusNavEntryDecorator()
    )
)

Claves de resultados

ResultEventBus identifica y enruta cada resultado con una clave. Los remitentes y los receptores hacen coincidir los resultados con la misma clave.

Puedes especificar las claves de resultado de dos maneras:

  • Claves explícitas: Puedes especificar una clave explícita (como resultKey = "pickup_address"). Usa claves explícitas cuando se muestren tipos comunes (como String, Boolean o primitivas) o cuando varios destinos muestren instancias diferentes del mismo tipo de datos.
  • Claves derivadas de tipos: Cuando no especificas una clave explícita, ResultEventBus genera automáticamente una clave con la representación toString del KClass del tipo de resultado (como Contact::class.toString()). Usa claves derivadas de tipos para tipos de datos distintos y específicos del dominio.

Cómo devolver resultados desde un destino

Para que los elementos componibles de la pantalla se puedan reutilizar y probar, no accedas a LocalResultEventBus directamente dentro de la IU de la pantalla. En su lugar, expón lambdas de devolución de llamada desde tu pantalla. En tu entryProvider, controla la devolución de llamada enviando el resultado con LocalResultEventBus.current y navegando hacia atrás.

Puedes enviar resultados con una clave de resultado explícita:

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()
        }
    )
}

También puedes enviar resultados con una clave derivada del 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()
        }
    )
}

Recibe resultados

Los destinos pueden consumir resultados con efectos basados en eventos o con observables basados en el estado.

API Comportamiento Casos de uso recomendados
ResultEffect En cola: Procesa todos los resultados emitidos para la clave en orden. Eventos únicos y efectos secundarios (como mostrar una barra de notificaciones o reenviar a un ViewModel)
conflateAsState Conflated: Descarta los resultados intermedios y conserva solo el resultado más reciente como State de Compose. Modificadores de estado de la IU ligeros (como etiquetas de filtro activas o anulaciones de selección)

Controla eventos únicos con ResultEffect

Usa ResultEffect cuando manejes eventos únicos, como activar estadísticas, mostrar una barra de notificaciones o reenviar un resultado a un ViewModel.

ResultEffect mantiene una cola para los resultados entrantes. Si se envían varios resultados para una clave determinada, ResultEffect procesa cada resultado en el orden en que se envió. Además, ResultEffect se ejecuta en un alcance de corrutina, lo que te permite llamar a funciones de suspensión directamente dentro del cuerpo del efecto.

Puedes escuchar los resultados asociados con una clave de resultado explícita:

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") }
    )
}

También puedes escuchar los resultados con una clave derivada del 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
    )
}

Cuando navegas entre destinos, ResultEffect se ejecuta a través de la siguiente secuencia del ciclo de vida:

  1. El remitente emite: El destino del remitente envía un resultado con resultBus.sendResult(resultKey = "pickup_address", address) y quita la pila de actividades.
  2. El receptor ingresa a la composición: El destino del receptor se convierte en la pantalla activa y ResultEffect comienza a escuchar los resultados.
  3. El receptor procesa los resultados: ResultEffect recibe y ejecuta su cuerpo de efecto para cada resultado enviado para esa clave, procesando todas las emisiones en el orden en que se enviaron.
  4. El receptor sale de la composición: Cuando se quita el destino del receptor de la pila de actividades, ResultEffect sale de la composición y deja de escuchar los resultados.

Observa los resultados más recientes como estado con conflateAsState

Cuando solo necesites el valor del resultado más reciente para modificar o filtrar directamente el estado de la IU local y quieras que Compose vuelva a componer automáticamente cada vez que se actualice el resultado, llama a conflateAsState en ResultEventBus.

Puedes observar los resultados asociados con una clave de resultado explícita:

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") }
    )
}

También puedes observar los resultados con una clave derivada del 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
    )
}

Elevador ResultEventBus

De forma predeterminada, rememberResultEventBusNavEntryDecorator crea y recuerda su propio ResultEventBus de forma interna con rememberResultEventBus.

Puedes crear y elevar un ResultEventBus de forma explícita cuando necesites hacer lo siguiente:

  • Pasa la instancia de ResultEventBus directamente a componentes no componibles o grafos de inserción de dependencias.
  • Envía o observa resultados de la estructura de la app de nivel superior (como una barra de la app o un panel lateral de navegación) fuera de la jerarquía de destino.

Para elevar ResultEventBus, créalo con rememberResultEventBus y pásalo 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
    )
)

Cómo administrar y borrar resultados

Cuando un destino consuma un resultado único, bórralo del bus de eventos con removeResult. Esto evita que el bus vuelva a entregar eventos pasados a observadores nuevos cuando los destinos vuelven a ingresar a la composición:

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")
    }
}

Puedes borrar los resultados por clave explícita (resultBus.removeResult(resultKey)) o por clave derivada del tipo (resultBus.removeResult<T>()). Para obtener detalles sobre la coincidencia de claves, consulta Claves de resultados.

Recetas

Para ver ejemplos de código ejecutables completos que demuestran diferentes estrategias de transferencia de resultados, consulta las siguientes recetas: