A partir da versão 1.2.0 do Navigation 3, é possível retornar resultados de destinos usando a API ResultEventBus.
O ResultEventBus oferece dois modelos de comunicação:
- Resultados baseados em eventos: para eventos únicos e temporários (como mostrar uma
snackbar de confirmação ou acionar um efeito colateral) usando
ResultEffect. - Resultados com base no estado: para observar o resultado mais recente à medida que o Compose
StateusaconflateAsState.
Configurar o barramento de eventos de resultado
Para disponibilizar ResultEventBus aos destinos combináveis, adicione o
rememberResultEventBusNavEntryDecorator à lista
de decoradores transmitidos ao NavDisplay. Isso fornece o conteúdo de cada
destino com um local de composição
LocalResultEventBus.
NavDisplay( /* ... */ entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberResultEventBusNavEntryDecorator() ) )
Chaves de resultado
O ResultEventBus identifica e encaminha cada resultado usando uma chave. Os remetentes e
destinatários correspondem aos resultados usando a mesma chave.
É possível especificar chaves de resultado de duas maneiras:
- Chaves explícitas: é possível especificar uma chave explícita (como
resultKey = "pickup_address"). Use chaves explícitas ao retornar tipos comuns (comoString,Booleanou primitivos) ou quando vários destinos retornam instâncias diferentes do mesmo tipo de dados. - Chaves derivadas de tipo: quando você não especifica uma chave explícita, o
ResultEventBusgera automaticamente uma chave usando a representaçãotoStringdoKClassdo tipo de resultado (comoContact::class.toString()). Use chaves derivadas de tipo para tipos de dados distintos e específicos do domínio.
Retornar resultados de um destino
Para manter os elementos combináveis da tela reutilizáveis e testáveis, não acesse
LocalResultEventBus diretamente na interface da tela. Em vez disso, exponha lambdas de callback
da sua tela. No seu entryProvider, processe o callback
enviando o resultado usando
LocalResultEventBus.current e voltando.
É possível enviar resultados usando uma chave 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() } ) }
Também é possível enviar resultados usando uma chave derivada de 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() } ) }
Receber resultados
Os destinos podem consumir resultados usando efeitos baseados em eventos ou observáveis baseados em estado.
| API | Comportamento | Casos de uso recomendados |
|---|---|---|
ResultEffect |
Em fila: processa todos os resultados emitidos para a chave em ordem. | Eventos únicos e efeitos colaterais (como mostrar uma snackbar ou encaminhar para um ViewModel). |
conflateAsState |
Conflated: descarta resultados intermediários e retém apenas o resultado mais recente como Compose State. |
Modificadores de estado da interface leves (como tags de filtro ativas ou substituições de seleção). |
Processar eventos únicos com ResultEffect
Use ResultEffect ao processar eventos únicos, como
acionar análises, mostrar uma snackbar ou encaminhar um resultado para um
ViewModel.
O ResultEffect mantém uma fila para os resultados recebidos. Se vários resultados forem enviados para uma determinada chave, o ResultEffect vai processar cada um deles na ordem em que foram enviados. Além disso, o ResultEffect é executado em um escopo de corrotina, o que permite
chamar funções de suspensão diretamente no corpo do efeito.
Você pode ouvir os resultados associados a uma chave 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") } ) }
Você também pode ouvir os resultados usando uma chave derivada de 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 ) }
Ao navegar entre destinos, o ResultEffect é executado na seguinte sequência de ciclo de vida:
- Emissão do remetente: o destino do remetente envia um resultado usando
resultBus.sendResult(resultKey = "pickup_address", address)e remove a backstack. - O receptor entra na composição: o destino do receptor se torna a
tela ativa, e
ResultEffectcomeça a detectar resultados. - O receptor processa os resultados:
ResultEffectrecebe e executa o corpo do efeito para cada resultado enviado para essa chave, processando todas as emissões na ordem em que foram enviadas. - O receptor sai da composição: quando o destino do receptor é removido
da backstack,
ResultEffectsai da composição e para de detectar resultados.
Observar os resultados mais recentes como estado com conflateAsState
Quando você só precisa do valor do resultado mais recente para modificar ou filtrar diretamente o estado da interface local
e quer que o Compose faça a recomposição automática sempre que o resultado for atualizado,
chame conflateAsState em ResultEventBus.
Você pode observar resultados associados a uma chave 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") } ) }
Você também pode observar os resultados usando uma chave derivada do 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
Por padrão, o rememberResultEventBusNavEntryDecorator cria e lembra
o próprio ResultEventBus internamente usando
rememberResultEventBus.
É possível criar e elevar explicitamente um ResultEventBus quando você precisa:
- Transmita a instância
ResultEventBusdiretamente para componentes não combináveis ou gráficos de injeção de dependência. - Envie ou observe resultados do scaffolding de apps de nível superior (como uma barra de apps ou um gaveta de navegação) fora da hierarquia de destino.
Para fazer o hoisting de ResultEventBus, crie-o usando rememberResultEventBus e transmita
para 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 ) )
Gerenciar e limpar resultados
Quando um destino consome um resultado único, limpe-o do barramento de eventos usando
removeResult. Isso evita que o barramento reenvie
eventos anteriores para novos observadores quando os destinos entram novamente na composição:
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") } }
Você pode limpar os resultados por chave explícita (resultBus.removeResult(resultKey)) ou
por chave derivada do tipo (resultBus.removeResult<T>()). Para detalhes sobre a correspondência
de chaves, consulte Chaves de resultado.
Receitas
Para exemplos de código executáveis completos que demonstram diferentes estratégias de transmissão de resultados, consulte as seguintes receitas:
- Receita de resultados baseada em eventos
- Receita de resultados com base em estado
- Receita de resultado serializável