You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose导航图作用域ViewModel如何优雅获取路由参数

composable(routeWithParameter) {
   // (A): 此处需要从路由中获取'args'参数
   // 我当前的解决方案是重新添加该参数:
   val args = it.arguments?.getString("argumentKey") // 从路由中获取'args'

   val state = it.getRememberedParent(navController) // 获取父导航栈条目
   
   state.arguments?.putString("argumentKey", args ) // 将'args'重新存入父状态的参数中
   
   val viewModel = hiltViewModel<TViewModel>(state)
   Log.d("MISC", "id =${System.identityHashCode(viewModel)}") // 与(B)中的实例相同
   ...
}
composable(route) {
   // (B):      
   val viewModel = hiltViewModel<TViewModel>(it.getRememberedParent(navController))
   Log.d("MISC", "id =${System.identityHashCode(viewModel)}") //与(A)中的实例相同
   ...
}
...

}

@Composable
fun NavBackStackEntry.getRememberedParent(navController: NavHostController): NavBackStackEntry {
val parentId = this.destination.parent!!.id
return remember(this) { navController.getBackStackEntry(parentId) }
}

ViewModel通过`SavedStateHandle`获取参数:
```kotlin
@HiltViewModel
class EditFarmViewModel @Inject constructor(
    state: SavedStateHandle,
) : ViewModel() {

 private val _args = checkNotNull(state.get<String>("argumentKey"))

}

为了让(A)和(B)拿到同一个ViewModel实例,我不能在(A)中直接调用不带state参数的hiltViewModel(),否则(B)会创建新的实例。我在官方文档里没找到对应的解决技巧。

我有两个疑问:

  • 如何在不重新插入参数的前提下,让ViewModel获取到(A)的路由参数?
  • 或者,如何在(A)中不用getBackStackEntry查找父条目,就能拿到同一个作用域的ViewModel实例,同时还能使用初始路由参数?

解决方案

方案一:将参数提升到父导航图路由(推荐)

最符合导航组件设计规范的方式,是把参数直接绑定到父导航图的路由上。这样当你导航到父导航图下的任何页面时,参数会自动存在于父导航栈条目的arguments中,ViewModel可以直接从父作用域的SavedStateHandle读取,完全不需要手动复制参数。

示例代码:

// 定义包含参数的父导航图
navigation(
    startDestination = "route_with_param",
    route = "parent_route/{argumentKey}"
) {
    composable("route_with_param") { backStackEntry ->
        // 直接获取父导航栈条目
        val parentBackStackEntry = backStackEntry.getRememberedParent(navController)
        // 从父作用域获取ViewModel,参数已自动存在于SavedStateHandle中
        val viewModel = hiltViewModel<EditFarmViewModel>(parentBackStackEntry)
        // 后续使用viewModel
    }
    composable("route_without_param") { backStackEntry ->
        val parentBackStackEntry = backStackEntry.getRememberedParent(navController)
        val viewModel = hiltViewModel<EditFarmViewModel>(parentBackStackEntry)
        // 后续使用viewModel
    }
}

导航到父导航图时,需要传入参数:

navController.navigate("parent_route/your_argument_value")

方案二:直接给ViewModel的SavedStateHandle设置参数

如果不想修改导航图结构,可以在(A)页面中获取到参数后,将其设置到父作用域ViewModel的SavedStateHandle中,只需确保只设置一次(避免页面重建时重复设置):

首先修改ViewModel,暴露SavedStateHandle:

@HiltViewModel
class EditFarmViewModel @Inject constructor(
    val savedStateHandle: SavedStateHandle, // 直接暴露SavedStateHandle
) : ViewModel() {
    private val _args: String
        get() = checkNotNull(savedStateHandle.get<String>("argumentKey"))
}

然后在(A)页面中:

composable(routeWithParameter) { backStackEntry ->
    val args = checkNotNull(backStackEntry.arguments?.getString("argumentKey"))
    val parentBackStackEntry = backStackEntry.getRememberedParent(navController)
    val viewModel = hiltViewModel<EditFarmViewModel>(parentBackStackEntry)
    
    // 仅在参数变化时设置一次
    LaunchedEffect(args) {
        viewModel.savedStateHandle.set("argumentKey", args)
    }
    
    // 后续使用viewModel
}

方案三:结合Hilt辅助注入传递参数

如果ViewModel有其他依赖,同时需要手动传递参数,可以使用Hilt的AssistedInject来实现,既保留依赖注入能力,又能传递路由参数:

  1. 修改ViewModel,添加AssistedInject和辅助工厂:
@HiltViewModel
class EditFarmViewModel @AssistedInject constructor(
    @Assisted state: SavedStateHandle,
    private val farmRepository: FarmRepository // 其他依赖
) : ViewModel() {
    private val _args = checkNotNull(state.get<String>("argumentKey"))

    // 定义辅助工厂
    @AssistedFactory
    interface Factory {
        fun create(state: SavedStateHandle): EditFarmViewModel
    }

    // 提供ViewModel工厂
    companion object {
        fun provideFactory(
            factory: Factory,
            argumentKey: String
        ): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
            @Suppress("UNCHECKED_CAST")
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                return factory.create(SavedStateHandle(mapOf("argumentKey" to argumentKey))) as T
            }
        }
    }
}
  1. 创建Hilt入口点:
@EntryPoint
@InstallIn(ActivityComponent::class)
interface ViewModelFactoryEntryPoint {
    fun editFarmViewModelFactory(): EditFarmViewModel.Factory
}
  1. 在(A)页面中使用:
composable(routeWithParameter) { backStackEntry ->
    val args = checkNotNull(backStackEntry.arguments?.getString("argumentKey"))
    val parentBackStackEntry = backStackEntry.getRememberedParent(navController)
    
    // 获取Hilt工厂
    val factory = EntryPointAccessors.fromActivity(
        LocalContext.current as Activity,
        ViewModelFactoryEntryPoint::class.java
    ).editFarmViewModelFactory()
    
    // 从父作用域获取ViewModel,传入参数
    val viewModel = viewModel<EditFarmViewModel>(
        viewModelStore = parentBackStackEntry.viewModelStore,
        factory = EditFarmViewModel.provideFactory(factory, args)
    )
    
    // 后续使用viewModel
}

内容的提问来源于stack exchange,提问作者pierre.b

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 11:05:00