Retornar resultados

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 State usa conflateAsState.

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 (como String, Boolean ou 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 ResultEventBus gera automaticamente uma chave usando a representação toString do KClass do tipo de resultado (como Contact::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:

  1. Emissão do remetente: o destino do remetente envia um resultado usando resultBus.sendResult(resultKey = "pickup_address", address) e remove a backstack.
  2. O receptor entra na composição: o destino do receptor se torna a tela ativa, e ResultEffect começa a detectar resultados.
  3. O receptor processa os resultados: ResultEffect recebe e executa o corpo do efeito para cada resultado enviado para essa chave, processando todas as emissões na ordem em que foram enviadas.
  4. O receptor sai da composição: quando o destino do receptor é removido da backstack, ResultEffect sai 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 ResultEventBus diretamente 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: