我正在使用众所周知的Dagger-ViewModelFactory模式来为所有活动中的所有ViewModel
注入工厂。
@ActivityScope
class ViewModelFactory @Inject constructor(
private val creators: MutableMap<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
return creator.get() as T
}
}
我的问题是,当我将工厂注入Activity
时,Dagger失败了,因为我将不使用的ViewModels
的对象提供者并不总是可访问的。这不是因为尚未添加包含提供程序的模块。
例如,我有一个 LogIn 活动和一个 SignUp 活动,这是我为它们添加子组件的方式:
@ContributesAndroidInjector(modules = [
ViewModelModule::class,
FirebaseModule::class,
LogInModule::class,
BindLogInModule::class
])
@ActivityScope
internal abstract fun loginActivityInjector(): LoginActivity
@ContributesAndroidInjector(modules = [
ViewModelModule::class,
FirebaseModule::class,
SignUpModule::class,
BindSignUpModule::class
])
@ActivityScope
internal abstract fun signUpActivityInjector(): SignUpActivity
请注意,当我为SignUpActivity
创建子组件时,我不添加模块LogInModule
,因为我不需要该模块中的绑定。
结果是我得到了错误
e:com.package.my.AppComponent.java:8:错误:[Dagger / MissingBinding]如果没有@Provides注释的方法,则无法提供com.package.my.login.domain.LogInAuthenticator。 公共抽象接口AppComponent扩展了dagger.android.AndroidInjector { ^ 组件中存在具有匹配键的绑定:com.package.my.di.ActivityInjectorsModule_LoginActivityInjector $ app_prodDebug.LoginActivitySubcomponent com.package.my.login.domain.LogInAuthenticator插入在 com.package.my.login.repository.LoginRepository(logInAuthenticator) com.package.my.login.repository.LoginRepository在以下位置注入 com.package.my.login.domain.LoginUseCase(loginRepository) com.package.my.login.domain.LoginUseCase在以下位置注入 com.package.my.login.presentation.LoginViewModel(loginUseCase) com.package.my.login.presentation.LoginViewModel在以下位置注入 com.package.my.di.ViewModelModule.provideLoginViewModel(viewModel) java.util.Map,javax.inject.Provider>在以下位置注入 com.package.my.di.ViewModelFactory(创建者) com.package.my.di.ViewModelFactory在以下位置注入 com.package.my.di.ViewModelModule.bindViewModelFactory $ app_prodDebug(factory) androidx.lifecycle.ViewModelProvider.Factory被注入 com.package.my.login.ui.SignUpActivity.viewModelFactory com.package.my.login.ui.SignUpActivity在以下位置注入 dagger.android.AndroidInjector.inject(T)[com.package.my.di.AppComponent→com.package.my.di.ActivityInjectorsModule_SignUpActivityInjector $ app_prodDebug.SignUpActivitySubcomponent]
发生这种情况是因为LogInAuthenticator
由LogInModule
提供。
这是否意味着即使我真的不需要在LogInModule
中创建GoogleSignInClient
,唯一的解决方案就是添加SignUpActivity
?
答案 0 :(得分:1)
您已声明两个@ContributesAndroidInjector
方法都依赖于ViewModelModule
。在ViewModelModule
内部,您已经声明了所有ViewModels
,这意味着,当Dagger想要为SignUpActivity
构建依赖关系树时,它还需要 you < / strong>明确提及应如何构造LoginViewModel
。发生这种情况是因为Dagger需要知道ViewModelModule
中声明的每个依赖项应该如何构造。
针对您的情况的解决方案将或包含在所有@ContributesAndroidInjector
声明中的所有模块(这是一个丑陋的方法),或者移动{ {1}}至SignUpViewModel
,并且SignUpModule
声明中不包含ViewModelModule
。
这是最适合我的设置。
首先,我创建了一个SignUpActivity
,所有功能模块都应包括在其专用的BaseActivityModule
类中:
@Module
然后,假设我们具有2个功能: Foo 和 Bar :
@Module
abstract class BaseActivityModule {
@Binds abstract fun bindsViewModelFactory(factory: MyViewModelFactory): ViewModelProvider.Factory
}
@Module
abstract class ActivitiesModule {
@PerActivity @ContributesAndroidInjector(modules = [FooModule::class])
abstract fun contributesFooActivity(): FooActivity
@PerActivity @ContributesAndroidInjector(modules = [BarModule::class])
abstract fun contributesBarActivity(): BarActivity
}
的实现类应以ViewModelProvider.Factory
为范围,因为每次在特定活动的范围内需要注入依赖项时,都应提供@PerActivity
的相同实例:
ViewModelProvider.Factory
以这种方式声明private typealias ViewModelProvidersMap = Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
@PerActivity
class MyViewModelFactory @Inject constructor(
private val creators: ViewModelProvidersMap
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var viewModelProvider = creators[modelClass]
if (viewModelProvider == null) {
val entries = creators.entries
val mapEntry = entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
} ?: throw IllegalArgumentException("Unknown model class $modelClass")
viewModelProvider = mapEntry.value
}
try {
@Suppress("UNCHECKED_CAST")
return viewModelProvider.get() as T
} catch (e: Throwable) {
throw IllegalArgumentException("Couldn't create ViewModel with specified class $modelClass", e)
}
}
}
的地方:
@PerActivity
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class PerActivity
和FooModule
声明如下:
BarModule
然后我们将
@Module(includes = [BaseActivityModule::class])
abstract class FooModule {
@Binds @IntoMap @ViewModelKey(FooViewModel::class)
abstract fun bindsFooViewModel(viewModel: FooViewModel): ViewModel
}
@Module(includes = [BaseActivityModule::class])
abstract class BarModule {
@Binds @IntoMap @ViewModelKey(BarViewModel::class)
abstract fun bindsBarViewModel(viewModel: BarViewModel): ViewModel
}
这样包含在ActivitiesModule
中:
AppComponent
通过这种方法,我们将
@Singleton
@Component(modules = [
AndroidInjectionModule::class,
ActivitiesModule::class
])
interface AppComponent {
...
}
的创建向下移动了一层:以前它位于最顶层的ViewModelProvider.Factory
中,现在每个子组件都将负责创建AppComponent
。 / p>
答案 1 :(得分:0)
为什么需要添加LoginModule的答案在于错误日志。错误日志跟踪依赖关系,如下所示:
SignUpActivity <-在-注入- ViewModelFactory <-在-注入- LoginViewModel <-在-注入- LoginUseCase <-在以下位置注入- LoginRepository <-在以下位置注入- LoginAuthenticator
上面的地图显示您需要添加LoginModule,因为Dagger需要它来将ViewModelFactory成功注入到SignUpActivity中。
编辑
从SignUpViewModel
中移动ViewModelModule
绑定,如下所示将其放置在SignUpModule中:
@Module(includes = [SignUpModule.BindsModule::class])
class SignUpModule {
// your other provides methods
@Module
interface BindsModule{
@Binds
@IntoMap
@ViewModelKey(SignUpViewModel::class)
fun signUpViewModel(signUpViewModel: SignUpViewModel): ViewModel
}
}
然后以这种方式添加子组件。注意ViewModelModule
已被排除
@ContributesAndroidInjector(modules = [
FirebaseModule::class,
SignUpModule::class,
BindSignUpModule::class
])
@ActivityScope
internal abstract fun signUpActivityInjector(): SignUpActivity