返回结果

从 Navigation 3 1.2.0 开始,您可以使用 ResultEventBus API 返回来自目的地的结果。

ResultEventBus 提供两种通信模型:

  • 基于事件的结果:对于短暂的一次性事件(例如显示确认信息条或触发附带效应),请使用 ResultEffect
  • 基于状态的结果:用于观察 Compose State 使用 conflateAsState 时的最新结果。

设置结果事件总线

如需使 ResultEventBus 可用于可组合目的地,请将 rememberResultEventBusNavEntryDecorator 添加到传递给 NavDisplay 的装饰器列表中。这会为每个目的地的内容提供 LocalResultEventBus CompositionLocal。

NavDisplay(
    /* ... */
    entryDecorators = listOf(
        rememberSaveableStateHolderNavEntryDecorator(),
        rememberResultEventBusNavEntryDecorator()
    )
)

结果键

ResultEventBus 使用键来标识和路由每个结果。发送者和接收者使用相同的密钥来匹配结果。

您可以通过两种方式指定结果键:

  • 显式键:您可以指定显式键(例如 resultKey = "pickup_address")。当返回常见类型(例如 StringBoolean 或基元)时,或者当多个目的地返回同一数据类型的不同实例时,请使用显式键。
  • 派生自类型的键:如果您未指定显式键,ResultEventBus 会使用结果类型的 KClass(例如 Contact::class.toString())的 toString 表示形式自动生成键。对于不同的特定于网域的数据类型,请使用派生自类型的键。

从目的地返回结果

为了保持屏幕可组合项的可重用性和可测试性,请勿在屏幕界面内直接访问 LocalResultEventBus。而是从界面公开回调 lambda。在 entryProvider 中,通过使用 LocalResultEventBus.current 发送结果并返回,来处理回调。

您可以使用显式结果键发送结果:

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()
        }
    )
}

您还可以使用派生自类型的键发送结果:

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()
        }
    )
}

接收结果

目的地可以使用基于事件的效果或基于状态的可观测对象来使用结果。

API 行为 推荐的用例
ResultEffect 已加入队列:按顺序处理为键发出的所有结果。 一次性事件和副作用(例如显示信息条或转发到 ViewModel)。
conflateAsState 合并:舍弃中间结果,仅保留最新结果作为 Compose State 轻量级界面状态修改器(例如有效过滤条件标记或选择替换)。

使用 ResultEffect 处理一次性事件

在处理一次性事件(例如触发分析、显示 snackbar 或将结果转发到 ViewModel)时,请使用 ResultEffect

ResultEffect 会维护一个用于接收传入结果的队列。如果针对给定键发送了多个结果,ResultEffect 会按发送顺序处理每个结果。此外,ResultEffect 在协程作用域中运行,因此您可以在效果主体内直接调用挂起函数。

您可以监听与明确的结果键相关联的结果:

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") }
    )
}

您还可以使用派生自类型的键来监听结果:

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
    )
}

在目的地之间导航时,ResultEffect 会按以下生命周期顺序执行:

  1. 发送方发出:发送方目的地使用 resultBus.sendResult(resultKey = "pickup_address", address) 发送结果并弹出返回堆栈。
  2. 接收器进入组合:接收器目的地成为有效屏幕,并且 ResultEffect 开始监听结果。
  3. 接收器处理结果ResultEffect 接收并执行针对相应键发送的每个结果的效果主体,并按发送顺序处理所有发射。
  4. 接收器离开组合:当接收器目的地从返回堆栈中弹出时,ResultEffect 会离开组合并停止监听结果。

使用 conflateAsState 将最新结果作为状态进行观测

如果您只需要最新的结果值来直接修改或过滤本地界面状态,并且希望 Compose 在结果更新时自动重组,请对 ResultEventBus 调用 conflateAsState

您可以观察与明确的结果键相关联的结果:

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") }
    )
}

您还可以使用派生自类型的键来观察结果:

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
    )
}

提升机 ResultEventBus

默认情况下,rememberResultEventBusNavEntryDecorator 会使用 rememberResultEventBus 在内部创建并记住自己的 ResultEventBus

在需要时,您可以明确创建并提升 ResultEventBus

  • ResultEventBus 实例直接传递到非可组合的组件或依赖注入图表中。
  • 在目的地层次结构之外发送或观察来自顶级应用框架(例如应用栏或导航抽屉)的结果。

如需提升 ResultEventBus,请使用 rememberResultEventBus 创建它并将其传递给 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
    )
)

管理和清除结果

当目的地使用一次性结果时,请使用 removeResult 从事件总线中清除该结果。这可防止总线在目的地重新进入组合时,向新的观测者重新传送过去的事件:

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")
    }
}

您可以通过显式键 (resultBus.removeResult(resultKey)) 或类型派生键 (resultBus.removeResult<T>()) 清除结果。如需详细了解键匹配,请参阅结果键

食谱

如需查看演示不同结果传递策略的完整可运行代码示例,请参阅以下食谱: