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
Statede Compose conconflateAsState.
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 (comoString,Booleano primitivos), o cuando varios destinos devuelvan diferentes instancias del mismo tipo de datos. - Claves derivadas del tipo: Cuando no especificas una clave explícita,
ResultEventBusgenera automáticamente una clave con la representacióntoStringdelKClassdel tipo de resultado (comoContact::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:
- 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. - El receptor ingresa a la composición: El destino del receptor se convierte en la pantalla activa y
ResultEffectcomienza a escuchar los resultados. - El receptor procesa los resultados:
ResultEffectrecibe 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. - El receptor abandona la composición: Cuando el destino del receptor se extrae de la pila de actividades,
ResultEffectabandona 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
ResultEventBusdirectamente 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:
- Receta de resultados basada en eventos
- Receta de resultados basada en el estado
- Receta de resultados serializables