Mostrar resultados

A partir de la versión 1.2.0 de Navigation 3, 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 como State de Compose con conflateAsState.

Configura el bus de eventos de resultados

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

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

Claves de resultado

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

Puedes especificar claves de resultados de dos maneras:

  • Claves explícitas: Puedes especificar una clave explícita (como resultKey = "pickup_address"). Usa claves explícitas cuando devuelvas tipos comunes (como String, Boolean o primitivos), o cuando varios destinos devuelvan diferentes instancias del mismo tipo de datos.
  • Claves derivadas del tipo: 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 del tipo para tipos de datos distintos y específicos del dominio.

Devuelve resultados de un destino

Para que los elementos componibles de la pantalla sean reutilizables y se puedan probar, no accedas a LocalResultEventBus directamente dentro de la IU de la pantalla. En cambio, 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 usando efectos basados en eventos o variables observables basadas 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 ViewModel)
conflateAsState Conflated: Descarta los resultados intermedios y conserva solo el resultado más reciente como Compose State. Modificadores de estado de la IU ligeros (como etiquetas de filtro activas o anulaciones de selección).

Cómo controlar 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 corrutinas, 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 se navega entre destinos, ResultEffect se ejecuta a través de la siguiente secuencia del ciclo de vida:

  1. Emisión del remitente: 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 el cuerpo del efecto para cada resultado enviado para esa clave, y procesa todas las emisiones en el orden en que se enviaron.
  4. El receptor abandona la composición: Cuando el destino del receptor se extrae de la pila de actividades, ResultEffect abandona la composición y deja de escuchar los resultados.

Observa los resultados más recientes como estado con conflateAsState

Cuando solo necesitas el valor del resultado más reciente para modificar o filtrar directamente el estado de la IU local y quieres que Compose se recompose 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 ResultEventBus directamente a los componentes no componibles o a los gráficos de inyección de dependencias.
  • Enviar u observar resultados desde el andamiaje 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
    )
)

Administra y borra los resultados

Cuando un destino consume un resultado único, bórralo del bus de eventos con removeResult. Esto evita que el bus vuelva a entregar eventos anteriores a los observadores nuevos cuando los destinos vuelven a ingresar en 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: