Retornar resultados

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

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 (como String, Boolean ou 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 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 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:

  1. O remetente emite: 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 o 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.

É 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 ResultEventBus diretamente 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: