从 Navigation 3 1.2.0 开始,您可以使用 ResultEventBus API 从目的地返回结果。
ResultEventBus 提供了两种通信模型:
- 基于事件的结果:对于瞬时性的一次性事件(例如显示确认信息提示控件或触发附带效应),请使用
ResultEffect。 - 基于状态的结果:用于使用
conflateAsState将最新结果作为 ComposeState进行观察。
设置结果事件总线
如需使 ResultEventBus 可用于可组合项目的地,请将 rememberResultEventBusNavEntryDecorator 添加到传递给 NavDisplay 的装饰器列表中。这会为每个目的地的内容提供 LocalResultEventBus CompositionLocal。
NavDisplay( /* ... */ entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberResultEventBusNavEntryDecorator() ) )
结果键
ResultEventBus 使用键来标识和路由每个结果。发送方和接收方使用相同的密钥匹配结果。
您可以通过以下两种方式指定结果键:
- 显式键:您可以指定显式键(例如
resultKey = "pickup_address")。在返回常见类型(例如String、Boolean或基元)时,或者在多个目的地返回同一数据类型的不同实例时,请使用显式键。 - 类型派生键:如果您未指定显式键,
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 |
Conflated:丢弃中间结果,仅保留最新结果作为 Compose State。 |
轻量级界面状态修饰符(例如活跃过滤条件标记或选择替换)。 |
使用 ResultEffect 处理一次性事件
在处理一次性事件(例如触发分析、显示信息提示控件或将结果转发到 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 会按以下生命周期序列执行:
- 发送方发出:发送方目的地使用
resultBus.sendResult(resultKey = "pickup_address", address)发送结果,并弹出返回堆栈。 - 接收器进入组合:接收器目的地成为活动屏幕,
ResultEffect开始监听结果。 - 接收器处理结果:
ResultEffect会接收并执行针对该键发送的每个结果的效果主体,并按发送顺序处理所有发出的结果。 - 接收器离开组合:当接收器目的地从返回堆栈中弹出时,
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>()) 清除结果。如需详细了解键匹配,请参阅结果键。
食谱
如需查看演示不同结果传递策略的完整可运行代码示例,请参阅以下方案: