Dagger Hilt:如何将ViewModel注入Adapter?
嘿,我明白你现在遇到的问题了——在Fragment里用Hilt注入ViewModel顺风顺水,但到Adapter这儿就各种报错,不管是用by viewModels()还是构造/字段注入都搞不定。这其实是因为Adapter和ViewModel的生命周期绑定逻辑不兼容,咱们一步一步来解决它~
为什么会出错?
先搞清楚根源:
by viewModels()是Fragment/Activity专属的扩展函数,它依赖这些组件的ViewModelStoreOwner身份来创建和管理ViewModel的生命周期,Adapter不属于这类生命周期组件,所以自然没法用这个方法。- Hilt的
@ViewModelInject是为了让Hilt在ViewModelProvider的体系下创建ViewModel,而ViewModel的作用域是和Fragment/Activity绑定的。Adapter不在这个作用域里,Dagger找不到对应的绑定关系,就会抛出MissingBinding错误。
而且从架构设计来说,Adapter本来就应该只负责UI渲染和简单的交互转发,直接持有ViewModel会让它和业务逻辑耦合,还容易引发内存泄漏,所以咱们换个更合理的方式来做。
最佳解决方案:通过回调让Fragment做中间层
核心思路是:Adapter只负责传递用户交互事件给Fragment,Fragment再调用ViewModel的方法;ViewModel的数据变化由Fragment观察,再同步给Adapter。这样既解耦,又符合MVVM的设计原则。
步骤1:定义交互回调接口
先写一个接口,用来让Adapter把用户操作传递给Fragment:
interface HomeListInteractionListener { fun onUserItemClicked(user: Users) // 可以根据需求添加更多回调,比如长按、按钮点击等 }
步骤2:修改Adapter,接收回调并移除ViewModel依赖
把Adapter里的ViewModel依赖换成回调接口,注入的时候传入这个接口的实现:
class HomeListAdapter @Inject constructor( private val interactionListener: HomeListInteractionListener ): BaseListAdapter<Users>( itemsSame = { old, new -> old.username == new.username }, contentsSame = { old, new -> old == new } ) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<Users> { // 创建ViewHolder的逻辑不变 } override fun onBindViewHolder(holder: BaseViewHolder<Users>, position: Int) { val user = getItem(position) holder.bind(user) // 比如点击item时触发回调 holder.itemView.setOnClickListener { interactionListener.onUserItemClicked(user) } } }
步骤3:在Fragment中实现回调并连接ViewModel
让Fragment实现刚才的回调接口,同时观察ViewModel的数据来更新Adapter:
@AndroidEntryPoint class HomeFragment : BaseFragment<FragmentHomeBinding, HomeViewModel>(R.layout.fragment_home), HomeListInteractionListener { private val viewModel: HomeViewModel by viewModels() @Inject lateinit var homeListAdapter: HomeListAdapter override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 给RecyclerView设置Adapter binding.recyclerView.adapter = homeListAdapter // 观察ViewModel中的用户数据,更新Adapter列表 viewModel.users.observe(viewLifecycleOwner) { userList -> homeListAdapter.submitList(userList) } } // 实现回调接口,在这里调用ViewModel的业务方法 override fun onUserItemClicked(user: Users) { viewModel.handleUserClick(user) // 比如ViewModel里的handleUserClick方法处理点击逻辑,比如跳转到详情、更新数据等 } }
特殊情况:一定要在Adapter里访问ViewModel?
如果因为某些特殊需求,必须在Adapter里获取ViewModel的实例(非常不推荐,尽量避免),可以通过Fragment传递ViewModel的弱引用,防止内存泄漏:
修改Adapter
class HomeListAdapter @Inject constructor(): BaseListAdapter<Users>( itemsSame = { old, new -> old.username == new.username }, contentsSame = { old, new -> old == new } ) { private var viewModelRef: WeakReference<HomeViewModel>? = null fun attachViewModel(viewModel: HomeViewModel) { viewModelRef = WeakReference(viewModel) } // 使用时先检查引用是否有效 private fun doSomethingWithViewModel() { viewModelRef?.get()?.let { viewModel -> // 在这里访问ViewModel的方法或数据 val someData = viewModel.someLiveData.value } } }
在Fragment中传递ViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) homeListAdapter.attachViewModel(viewModel) }
总结
尽量遵循Adapter只做UI相关的事的原则,通过回调让Fragment作为Adapter和ViewModel的桥梁,这样不仅能解决Hilt注入的问题,还能让代码结构更清晰,避免生命周期相关的坑。
内容的提问来源于stack exchange,提问作者Pehr Sibusiso

