如何使用传参自定义工厂的navGraphViewModels获取共享ViewModel
问题根因
崩溃的核心原因是Kotlin属性委托的懒加载特性:你在群组列表页用by navGraphViewModels(R.id.chat) { factory }声明ViewModel时,只是注册了委托规则,只要在跳转前没有主动访问过这个chatVM属性,委托逻辑就不会触发,导航图对应的ViewModel存储区里根本不会生成MessagesViewModel实例。
等跳转到消息页后,消息页调用navGraphViewModels(R.id.chat)时没有传入自定义工厂,系统查找不到现成实例,就会调用默认的无参构造工厂尝试创建实例,自然就抛出没有零参构造函数的异常。
可行解决方案
方案1:最小改动版,跳转前提前触发VM初始化
如果你的消息页只有从群组列表跳转这一个入口,只需要在列表页执行导航动作前,主动访问一次声明的chatVM属性,触发委托完成实例创建即可:
val factory = MessagesViewModelFactory(item.group.id!!) val chatVM: MessagesViewModel by navGraphViewModels(R.id.chat) { factory } // 主动访问VM触发初始化,将实例存入导航图作用域 chatVM // 直接访问属性即可,不需要额外调用方法 val action = MessageGroupsFragmentDirections.actionNavigationSettingsToMessagesFragment() findNavController().navigate(action)
消息页原有代码不需要任何修改,直接获取就能拿到已经创建好的同实例ViewModel。
注意:这个方案存在路径依赖,如果后续新增其他入口直接打开消息页,没有经过列表页的初始化逻辑,依然会触发崩溃。
方案2:稳妥版,通过导航参数传递群组ID,双端统一工厂逻辑
这是生产环境更推荐的实现方式,不依赖前一个页面的初始化逻辑,从根源避免异常:
- 首先在导航配置文件中,给MessagesFragment添加
groupId类型的导航参数,和你的群组ID实际类型匹配 - 列表页跳转时将群组ID作为参数传入:
val action = MessageGroupsFragmentDirections .actionNavigationSettingsToMessagesFragment(groupId = item.group.id!!) findNavController().navigate(action)
- 消息页获取ViewModel时,先从导航参数中取出群组ID,构造对应的自定义工厂传入即可:
// 从导航参数取当前页面绑定的群组ID val args: MessagesFragmentArgs by navArgs() val viewModel: MessagesViewModel by navGraphViewModels(R.id.chat) { MessagesViewModelFactory(args.groupId) }
这种写法下,不管是列表页还是消息页第一次访问这个导航图作用域的ViewModel,都会用带正确群组ID的工厂创建实例,后续两个页面访问到的都是同一个共享实例,既满足VM共享的需求,也不会出现入口依赖导致的崩溃。
内容的提问来源于stack exchange,提问作者Msencenb
相关产品推荐
相关产品推荐

