Navigation 3中向Hilt ViewModel传递参数的可行方案咨询
我之前升级到Navigation 3时也碰到过一模一样的问题,折腾了好一阵才捋清楚门道,分享几个亲测有效的解决办法给你:
1. 结合@AssistedInject + 绑定导航条目解决缓存问题
你提到用@Assisted会导致ViewModel被 memoized 不响应新路由,核心问题其实是没把ViewModel实例和当前导航条目绑定起来。正确的操作应该是:
首先,给ViewModel加上@AssistedInject构造,同时声明依赖和@Assisted修饰的SavedStateHandle:
class MyViewModel @AssistedInject constructor( @Assisted private val savedStateHandle: SavedStateHandle, private val myRepository: MyRepository // 你的其他DI依赖 ) : ViewModel() { // 从SavedStateHandle获取路由参数 val param = savedStateHandle.get<String>("param_key") } // 配套定义一个Factory类供Hilt使用 @AssistedFactory interface MyViewModelFactory { fun create(savedStateHandle: SavedStateHandle): MyViewModel }
然后在Compose页面中,先获取当前导航条目,再通过这个条目获取ViewModel,确保每个路由实例对应专属的ViewModel:
composable("my_route/{param_key}") { navBackStackEntry -> val viewModel = hiltViewModel<MyViewModel>(navBackStackEntry) MyScreen(viewModel) }
这样每次进入新路由条目,都会创建/复用对应条目的ViewModel,不会再出现缓存导致的旧内容问题。
2. 直接用SavedStateHandle获取路由参数(无需@Assisted)
其实Navigation 3里SavedStateHandle依然可以正常使用,你之前可能是没正确把导航条目和ViewModel关联起来。只要在获取ViewModel时传入当前的navBackStackEntry,Hilt会自动把对应条目的SavedStateHandle注入到ViewModel中:
ViewModel定义:
@HiltViewModel class DetailViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle, private val repository: ItemRepository ) : ViewModel() { // 直接从SavedStateHandle读取路由参数 val itemId = savedStateHandle.get<String>("item_id") ?: "" }
Compose页面中:
composable( route = "detail/{item_id}", arguments = listOf(navArgument("item_id") { type = NavType.StringType }) ) { backStackEntry -> // 传入backStackEntry,让Hilt绑定对应的SavedStateHandle val viewModel = hiltViewModel<DetailViewModel>(backStackEntry) DetailScreen(viewModel) }
这种方式最简洁,适合传递路由参数的场景,完全不需要额外的@Assisted配置。
3. 正确使用rememberViewModelStoreNavEntryDecorator()
你提到用这个方法会影响其他ViewModel,是因为可能全局应用了这个装饰器。正确的做法是只针对需要按路由作用域的ViewModel单独使用:
composable("specific_route") { navEntry -> val viewModel = rememberViewModelStoreNavEntryDecorator(navEntry) { viewModelStoreOwner -> hiltViewModel<SpecificViewModel>(viewModelStoreOwner = viewModelStoreOwner) } SpecificScreen(viewModel) }
这样只会让SpecificViewModel的生命周期绑定到当前路由条目,其他ViewModel依然沿用默认的作用域,不会互相干扰。
总结一下,最推荐的是第二种方式(直接用SavedStateHandle+绑定导航条目),既满足DI需求,又能轻松获取路由参数;如果需要传递非路由参数(比如自定义对象),再用第一种@AssistedInject的方案。
内容来源于stack exchange

