在不同的活动中注入 ViewModelFactory
Injecting ViewModelFactory in different activities
我正在使用著名的 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 activity 和一个 SignUp activity,这是我为它们添加子组件的方式:
@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: error: [Dagger/MissingBinding] com.package.my.login.domain.LogInAuthenticator cannot be provided without an @Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector {
^
A binding with matching key exists in component: com.package.my.di.ActivityInjectorsModule_LoginActivityInjector$app_prodDebug.LoginActivitySubcomponent
com.package.my.login.domain.LogInAuthenticator is injected at
com.package.my.login.repository.LoginRepository(logInAuthenticator)
com.package.my.login.repository.LoginRepository is injected at
com.package.my.login.domain.LoginUseCase(loginRepository)
com.package.my.login.domain.LoginUseCase is injected at
com.package.my.login.presentation.LoginViewModel(loginUseCase)
com.package.my.login.presentation.LoginViewModel is injected at
com.package.my.di.ViewModelModule.provideLoginViewModel(viewModel)
java.util.Map,javax.inject.Provider> is injected at
com.package.my.di.ViewModelFactory(creators)
com.package.my.di.ViewModelFactory is injected at
com.package.my.di.ViewModelModule.bindViewModelFactory$app_prodDebug(factory)
androidx.lifecycle.ViewModelProvider.Factory is injected at
com.package.my.login.ui.SignUpActivity.viewModelFactory
com.package.my.login.ui.SignUpActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.package.my.di.AppComponent → com.package.my.di.ActivityInjectorsModule_SignUpActivityInjector$app_prodDebug.SignUpActivitySubcomponent]
发生这种情况是因为 LogInAuthenticator
是由 LogInModule
提供的。
这是否意味着即使我真的不需要在 SignUpActivity
中创建 GoogleSignInClient
,唯一的解决方案是添加 LogInModule
?
您需要添加登录模块的原因在您的错误日志中。错误日志跟踪依赖关系如下:
SignUpActivity <--注入-- ViewModelFactory <--注入-- LoginViewModel <--注入-- LoginUseCase <--注入-- LoginRepository <--注入-- 登录验证器
上图显示您需要添加 LoginModule,因为 Dagger 需要它才能成功将您的 ViewModelFactory 注入到您的 SignUpActivity 中。
编辑
从您的 ViewModelModule
移动 SignUpViewModel
绑定并将其放置在您的 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
您已声明两个 @ContributesAndroidInjector
方法都依赖于 ViewModelModule
。在 ViewModelModule
中你已经声明了所有 ViewModels
,这意味着当 Dagger 想要为 SignUpActivity
构建依赖树时它也需要 你 明确提及 LoginViewModel
应该如何构建。发生这种情况是因为 Dagger 需要知道应该如何构建 ViewModelModule
中声明的每个依赖项。
您的解决方案是在所有 @ContributesAndroidInjector
声明中包含所有模块(这是一种丑陋的方法), 或 ,或者,移动SignUpViewModel
到 SignUpModule
的提供者方法并且不包括 SignUpActivity
声明的 ViewModelModule
。
这是适合我的设置。
首先,我创建了一个 BaseActivityModule
,所有功能模块都应该 在其专用的 @Module
class 中包含:
@Module
abstract class BaseActivityModule {
@Binds abstract fun bindsViewModelFactory(factory: MyViewModelFactory): ViewModelProvider.Factory
}
然后,假设我们有 2 个特征:Foo 和 Bar:
@Module
abstract class ActivitiesModule {
@PerActivity @ContributesAndroidInjector(modules = [FooModule::class])
abstract fun contributesFooActivity(): FooActivity
@PerActivity @ContributesAndroidInjector(modules = [BarModule::class])
abstract fun contributesBarActivity(): BarActivity
}
ViewModelProvider.Factory
的实现 class 应在 @PerActivity
范围内,因为每次需要注入依赖项时都应提供 ViewModelProvider.Factory
的相同实例特定范围 activity:
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
,现在每个子组件都将负责创建 ViewModelProvider.Factory
.
我正在使用著名的 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 activity 和一个 SignUp activity,这是我为它们添加子组件的方式:
@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: error: [Dagger/MissingBinding] com.package.my.login.domain.LogInAuthenticator cannot be provided without an @Provides-annotated method. public abstract interface AppComponent extends dagger.android.AndroidInjector { ^ A binding with matching key exists in component: com.package.my.di.ActivityInjectorsModule_LoginActivityInjector$app_prodDebug.LoginActivitySubcomponent com.package.my.login.domain.LogInAuthenticator is injected at com.package.my.login.repository.LoginRepository(logInAuthenticator) com.package.my.login.repository.LoginRepository is injected at com.package.my.login.domain.LoginUseCase(loginRepository) com.package.my.login.domain.LoginUseCase is injected at com.package.my.login.presentation.LoginViewModel(loginUseCase) com.package.my.login.presentation.LoginViewModel is injected at com.package.my.di.ViewModelModule.provideLoginViewModel(viewModel) java.util.Map,javax.inject.Provider> is injected at com.package.my.di.ViewModelFactory(creators) com.package.my.di.ViewModelFactory is injected at com.package.my.di.ViewModelModule.bindViewModelFactory$app_prodDebug(factory) androidx.lifecycle.ViewModelProvider.Factory is injected at com.package.my.login.ui.SignUpActivity.viewModelFactory com.package.my.login.ui.SignUpActivity is injected at dagger.android.AndroidInjector.inject(T) [com.package.my.di.AppComponent → com.package.my.di.ActivityInjectorsModule_SignUpActivityInjector$app_prodDebug.SignUpActivitySubcomponent]
发生这种情况是因为 LogInAuthenticator
是由 LogInModule
提供的。
这是否意味着即使我真的不需要在 SignUpActivity
中创建 GoogleSignInClient
,唯一的解决方案是添加 LogInModule
?
您需要添加登录模块的原因在您的错误日志中。错误日志跟踪依赖关系如下:
SignUpActivity <--注入-- ViewModelFactory <--注入-- LoginViewModel <--注入-- LoginUseCase <--注入-- LoginRepository <--注入-- 登录验证器
上图显示您需要添加 LoginModule,因为 Dagger 需要它才能成功将您的 ViewModelFactory 注入到您的 SignUpActivity 中。
编辑
从您的 ViewModelModule
移动 SignUpViewModel
绑定并将其放置在您的 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
您已声明两个 @ContributesAndroidInjector
方法都依赖于 ViewModelModule
。在 ViewModelModule
中你已经声明了所有 ViewModels
,这意味着当 Dagger 想要为 SignUpActivity
构建依赖树时它也需要 你 明确提及 LoginViewModel
应该如何构建。发生这种情况是因为 Dagger 需要知道应该如何构建 ViewModelModule
中声明的每个依赖项。
您的解决方案是在所有 @ContributesAndroidInjector
声明中包含所有模块(这是一种丑陋的方法), 或 ,或者,移动SignUpViewModel
到 SignUpModule
的提供者方法并且不包括 SignUpActivity
声明的 ViewModelModule
。
这是适合我的设置。
首先,我创建了一个 BaseActivityModule
,所有功能模块都应该 在其专用的 @Module
class 中包含:
@Module
abstract class BaseActivityModule {
@Binds abstract fun bindsViewModelFactory(factory: MyViewModelFactory): ViewModelProvider.Factory
}
然后,假设我们有 2 个特征:Foo 和 Bar:
@Module
abstract class ActivitiesModule {
@PerActivity @ContributesAndroidInjector(modules = [FooModule::class])
abstract fun contributesFooActivity(): FooActivity
@PerActivity @ContributesAndroidInjector(modules = [BarModule::class])
abstract fun contributesBarActivity(): BarActivity
}
ViewModelProvider.Factory
的实现 class 应在 @PerActivity
范围内,因为每次需要注入依赖项时都应提供 ViewModelProvider.Factory
的相同实例特定范围 activity:
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
,现在每个子组件都将负责创建 ViewModelProvider.Factory
.