结果回传
这篇文档告诉你:页面 A 跳到页面 B,B 做完事把结果带回 A,用到的代码都在这里:
- 路由声明:
navigation/routes/DemoRoutes.kt(NavigationResult、NavigationWithArgs(goodsId)) - 发送结果:
feature/demo/viewmodel/NavigationResultViewModel.kt - 接收结果:
feature/main/view/NavigationDemoScreen.kt - 核心能力:
navigation/AppNavigator.kt、navigation/NavigationResultKey.kt、navigation/extension/NavigationResultExt.kt - ResultKey 示例:
navigation/results/DemoResultKey.kt
三步完成“先跳转再回传”
- 子页面发送结果并返回
kotlin
// feature/demo/viewmodel/NavigationResultViewModel.kt
fun sendResultAndBack() {
popBackStackWithResult(
DemoResultKey, // 声明好的 Key
DemoResult(id = 9527, message = "这是回传的结果") // 要带回去的数据
)
}popBackStackWithResult写在BaseViewModel里,内部通过AppNavigator派发事件。- NavHost 收到事件后会:把结果序列化写进
previousBackStackEntry.savedStateHandle,然后popBackStack()。
- 父页面在 Route 层接收
kotlin
// feature/main/view/NavigationDemoScreen.kt 的 Route 函数里
navController.observeResult(DemoResultKey) { result ->
viewModel.onResultReceived(result) // 更新 StateFlow<DemoResult?>
}observeResult会在页面重新可见时读取savedStateHandle,用 Key 的反序列化方法还原类型,回调一次后自动 remove,保证“一次性消费”。
- UI 展示
kotlin
// 同文件,UI 层
demoResult?.let { DemoResultBanner(it) } // 有结果就渲染卡片自己定义一个 ResultKey
kotlin
// navigation/results/DemoResultKey.kt
object DemoResultKey : NavigationResultKey<DemoResult> {
override fun serialize(value: DemoResult): Any = Json.encodeToString(value) // 写入前转成字符串
override fun deserialize(raw: Any): DemoResult = Json.decodeFromString(raw as String)
}
@Serializable
data class DemoResult(val id: Long, val message: String)- 基础类型(Boolean/Int/String)可以不重写;复杂对象可以像上面一样转 JSON。
key默认用类名,避免手写字符串。
带参数跳转 + 回传,一套写法
- 路由:
@Serializable data class NavigationWithArgs(val goodsId: Long) - 子页取参:
savedStateHandle.toRoute<DemoRoutes.NavigationWithArgs>() - 如果还要回传:继续用
popBackStackWithResult(MyKey, payload),父页用observeResult(MyKey)。
刷新信号:用现成的 RefreshResultKey
- 内置
RefreshResultKey : NavigationResultKey<Boolean>,等价于refresh=true。 - 父页可调用基类
observeRefreshState(backStackEntry);子页执行popBackStackWithResult(RefreshResultKey, true),父页自动刷新,只消费一次。
注意
- 结果越小越好:ID、布尔、轻量 DTO,别传大对象。
- 多层导航时,把
observeResult挂在真正需要刷新的那层NavController.currentBackStackEntry。 - 需要不同业务的刷新信号时,另起一个
NavigationResultKey,不要混用全局的RefreshResultKey。
API 参考(和代码同步)
NavigationResultKey
NavigationResultKey<T> 将字符串 key 与具体类型绑定,避免魔法字符串。定义示例:
kotlin
object GoodsResultKey : NavigationResultKey<Long>使用步骤:
- 发送结果(通常在子页面 ViewModel 内)kotlin
fun finishWithGoods(goodsId: Long) { popBackStackWithResult(GoodsResultKey, goodsId) } - 接收结果(推荐用
observeResult扩展)kotlin如果在非 Compose 场景,也可以直接从navController.observeResult(GoodsResultKey) { id -> viewModel.refreshRow(id) }currentBackStackEntry.savedStateHandle读取并消费。
NavigationResultKey 可以重写 serialize / deserialize,在需要传递复杂对象(例如转 JSON)时控制序列化方式。
RefreshResultKey 与 observeRefreshState
RefreshResultKey 是一个内置的 NavigationResultKey<Boolean>,语义等价 refresh=true。BaseNetWorkViewModel / BaseNetWorkListViewModel 已封装:
kotlin
fun observeRefreshState(
backStackEntry: NavBackStackEntry?,
key: NavigationResultKey<Boolean> = RefreshResultKey
)在 Route 层使用:
kotlin
val backStackEntry = remember { navController.currentBackStackEntry }
LaunchedEffect(backStackEntry) {
viewModel.observeRefreshState(backStackEntry)
}当子页执行 popBackStackWithResult(RefreshResultKey, true) 时,父页会自动调用 executeRequest() 再拉一次数据,且该刷新信号只消费一次。
适用场景举例
- 商品详情修改收藏状态后,把商品 ID 回传给列表刷新。
- 评论页返回时,用
RefreshResultKey通知详情页重新拉评论。 - 选择地址/支付方式/优惠券等“一次性结果”回传。
掌握这套写法,你可以在 Compose 中实现安全、可测试的“跳转 + 带结果返回”流程。