A partir do Navigation 3 1.2.0, é possível retornar
resultados de destinos usando a API
ResultEventBus.
O ResultEventBus oferece dois modelos de comunicação:
- Resultados baseados em eventos: para eventos temporários e únicos (como mostrar uma
snackbar de confirmação ou acionar um efeito colateral) usando
ResultEffect. - Resultados baseados em estado: para observar o resultado mais recente como o Compose
StateusandoconflateAsState.
Configurar o barramento de eventos de resultado
Para disponibilizar ResultEventBus para os destinos combináveis, adicione o
rememberResultEventBusNavEntryDecorator à lista
de decoradores transmitidos para o NavDisplay. Isso fornece ao conteúdo de cada
destino uma composição local
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 as 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 retornarem 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 de tela reutilizáveis e testáveis, não acesse
LocalResultEventBus diretamente na interface da tela. Em vez disso, exponha lambdas de callback
da tela. No 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 |
Na 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 os resultados intermediários e retém apenas o resultado mais recente como o State do Compose. |
Modificadores leves de estado da interface (como tags de filtro ativo 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 resultado na ordem em que foi
enviado. Além disso, ResultEffect é executado em um escopo de corrotina, o que permite
chamar funções de suspensão diretamente no corpo do efeito.
É possível detectar 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") } ) }
Também é possível detectar 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:
- O remetente emite: 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 o
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.
É possível observar os 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") } ) }
Também é possível observar os resultados usando uma chave derivada de 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. - Enviar ou observar resultados do scaffolding de nível superior do app (como uma barra de apps ou uma gaveta de navegação) fora da hierarquia de destino.
Para elevar ResultEventBus, crie-o usando rememberResultEventBus e transmita-o
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 consumir um resultado único, limpe-o do barramento de eventos usando
removeResult. Isso impede que o barramento envie novamente
eventos anteriores para novos observadores quando os destinos voltarem à 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") } }
É possível limpar os resultados por chave explícita (resultBus.removeResult(resultKey)) ou
por chave derivada de tipo (resultBus.removeResult<T>()). Para detalhes sobre a correspondência
de chaves, consulte Chaves de resultado.
Receitas
Para ver exemplos de código executáveis completos que demonstram diferentes estratégias de transmissão de resultados, consulte as seguintes receitas:
- Receita de resultado baseada em eventos
- Receita de resultado com base no estado
- Receita de resultado serializável