无法通过HiltViewModel构造函数传字符串:Dagger依赖缺失错误
Dagger 2迁移Hilt时ViewModel注入失败问题
问题背景
正在将Android项目从Dagger 2迁移到Hilt,大部分迁移过程顺利,但最后一个ViewModel转换时遇到注入错误。
ContactDetailViewModel代码
@HiltViewModel class ContactDetailViewModel @Inject constructor( application: Application, contactId: String?, name: String?, refId: String, ) : NetworkBaseViewModel(application) { .... (非DI相关代码) @Inject lateinit var client: ApolloClient .... (非DI相关代码及构造参数引用) }
ContactDetailVMFactory代码
@Singleton class ContactDetailVMFactory @Inject constructor( val application: Application, val contactId: String?, val name: String?, val refId: String ) : ViewModelProvider.AndroidViewModelFactory(application) { override fun <T : ViewModel> create(modelClass: Class<T>): T { return ContactDetailViewModel(application, contactId, name, refId) as T } }
ContactDetailEditFragment代码
@AndroidEntryPoint open class ContactDetailEditFragment: Fragment() { val args: ContactDetailEditFragmentArgs by navArgs() open val vm by activityViewModels<ContactDetailViewModel>( factoryProducer = { ContactDetailVMFactory( application = requireActivity().application, contactId = args.contactId, name = args.name, refId = args.contactId ?: args.vmKey ) }, key = { args.vmKey } ) .... @Inject lateinit var client: ApolloClient .... }
错误构建输出
/BaseApplication_HiltComponents.java:141: error: [Dagger/MissingBinding] java.lang.String cannot be provided without an @Inject constructor or an @Provides-annotated method. public abstract static class SingletonC implements BaseApplication_GeneratedInjector, ^ java.lang.String is injected at com.companyname.contacts.vm.ContactDetailViewModel(…, name, …) com.companyname.contacts.vm.ContactDetailViewModel is injected at com.companyname.contacts.vm.ContactDetailViewModel_HiltModules.BindsModule.binds(vm) @dagger.hilt.android.internal.lifecycle.HiltViewModelMap java.util.Map<java.lang.String,javax.inject .Provider<androidx.lifecycle.ViewModel>> is requested at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint .getHiltViewModelMap() [com.companyname.BaseApplication_HiltComponents.SingletonC → com.companyname .BaseApplication_HiltComponents.ActivityRetainedC → com.companyname.BaseApplication_HiltComponents .ViewModelC] It is also requested at: com.companyname.contacts.vm.ContactDetailViewModel(…, contactId, …) com.companyname.contacts.vm.ContactDetailViewModel(…, refId)
疑问
已经研究多日,其他传递字符串的HiltViewModel都正常,怀疑是ViewModelFactory的问题。
解决方案
问题核心是**@HiltViewModel注解与自定义Factory的冲突**:
- 加了
@HiltViewModel后,Hilt会自动生成注入逻辑,尝试编译期注入ViewModel构造参数。但contactId、name、refId是Fragment导航传来的运行时动态值,Hilt无法在编译期提供绑定,因此报错。 - 自定义Factory被标记为
@Singleton,Hilt会尝试注入其构造参数,同样无法提供动态字符串,进一步触发错误。
正确改造步骤
步骤1:修改ViewModel,移除@HiltViewModel注解
改用自定义Factory传递动态参数,同时把ApolloClient移到构造参数中,避免ViewModel内部二次注入:
class ContactDetailViewModel @Inject constructor( application: Application, contactId: String?, name: String?, refId: String, val client: ApolloClient ) : NetworkBaseViewModel(application) { .... (非DI相关代码及构造参数引用) }
步骤2:改造自定义Factory,使用@AssistedInject处理动态参数
用@AssistedInject区分静态依赖(由Hilt注入)和动态参数(运行时传入):
class ContactDetailVMFactory @AssistedInject constructor( private val application: Application, private val client: ApolloClient, @Assisted private val contactId: String?, @Assisted private val name: String?, @Assisted private val refId: String ) : ViewModelProvider.AndroidViewModelFactory(application) { override fun <T : ViewModel> create(modelClass: Class<T>): T { return ContactDetailViewModel(application, contactId, name, refId, client) as T } // 定义辅助工厂接口,用于在Fragment中传递动态参数 @AssistedFactory interface Factory { fun create(contactId: String?, name: String?, refId: String): ContactDetailVMFactory } }
步骤3:修改Fragment中的ViewModel获取逻辑
通过Hilt注入辅助工厂,再创建带动态参数的ViewModel Factory:
@AndroidEntryPoint open class ContactDetailEditFragment: Fragment() { val args: ContactDetailEditFragmentArgs by navArgs() // 注入辅助工厂 @Inject lateinit var vmFactory: ContactDetailVMFactory.Factory open val vm by activityViewModels<ContactDetailViewModel>( factoryProducer = { vmFactory.create( contactId = args.contactId, name = args.name, refId = args.contactId ?: args.vmKey ) }, key = { args.vmKey } ) .... }
其他ViewModel正常的原因
如果其他传递字符串的ViewModel没有问题,大概率是它们的字符串参数是编译期可提供的静态值(比如通过@Provides注解提供的常量),或者没有同时使用@HiltViewModel和自定义Factory,而是采用了@AssistedInject的正确方式处理动态参数。
内容的提问来源于stack exchange,提问作者petestmart
相关产品推荐
相关产品推荐

