Skip to content

结果回传

这篇文档告诉你:页面 A 跳到页面 B,B 做完事把结果带回 A,用到的代码都在这里:

  • 路由声明:navigation/routes/DemoRoutes.ktNavigationResultNavigationWithArgs(goodsId)
  • 发送结果:feature/demo/viewmodel/NavigationResultViewModel.kt
  • 接收结果:feature/main/view/NavigationDemoScreen.kt
  • 核心能力:navigation/AppNavigator.ktnavigation/NavigationResultKey.ktnavigation/extension/NavigationResultExt.kt
  • ResultKey 示例:navigation/results/DemoResultKey.kt

三步完成“先跳转再回传”

  1. 子页面发送结果并返回
kotlin
// feature/demo/viewmodel/NavigationResultViewModel.kt
fun sendResultAndBack() {
    popBackStackWithResult(
        DemoResultKey,                              // 声明好的 Key
        DemoResult(id = 9527, message = "这是回传的结果") // 要带回去的数据
    )
}
  • popBackStackWithResult 写在 BaseViewModel 里,内部通过 AppNavigator 派发事件。
  • NavHost 收到事件后会:把结果序列化写进 previousBackStackEntry.savedStateHandle,然后 popBackStack()
  1. 父页面在 Route 层接收
kotlin
// feature/main/view/NavigationDemoScreen.kt 的 Route 函数里
navController.observeResult(DemoResultKey) { result ->
    viewModel.onResultReceived(result)             // 更新 StateFlow<DemoResult?>
}
  • observeResult 会在页面重新可见时读取 savedStateHandle,用 Key 的反序列化方法还原类型,回调一次后自动 remove,保证“一次性消费”。
  1. 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<T> 将字符串 key 与具体类型绑定,避免魔法字符串。定义示例:

kotlin
object GoodsResultKey : NavigationResultKey<Long>

使用步骤:

  1. 发送结果(通常在子页面 ViewModel 内)
    kotlin
    fun finishWithGoods(goodsId: Long) {
        popBackStackWithResult(GoodsResultKey, goodsId)
    }
  2. 接收结果(推荐用 observeResult 扩展)
    kotlin
    navController.observeResult(GoodsResultKey) { id ->
        viewModel.refreshRow(id)
    }
    如果在非 Compose 场景,也可以直接从 currentBackStackEntry.savedStateHandle 读取并消费。

NavigationResultKey 可以重写 serialize / deserialize,在需要传递复杂对象(例如转 JSON)时控制序列化方式。

RefreshResultKey 与 observeRefreshState

RefreshResultKey 是一个内置的 NavigationResultKey<Boolean>,语义等价 refresh=trueBaseNetWorkViewModel / 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 中实现安全、可测试的“跳转 + 带结果返回”流程。