在不同的活动中注入ViewModelFactory



我正在使用众所周知的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
}
}

我遇到的问题是,当我将工厂注入ActivityDagger时会失败,因为我不打算使用的ViewModels对象的提供者并不总是可以访问的。它们不是因为包含提供程序的模块尚未添加。

例如,我有一个登录活动和一个注册

@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]com.package.mi.login.domain.LogInAuthenticator不能在没有@Providers注释方法的情况下提供。公共抽象接口AppComponent扩展dagger.android.AndroidInjector{^组件:com.package.my.di.ActivityInjectorsModule_LoginActivityInject$app_prodDebug.LognActivitySubcomponent中存在具有匹配密钥的绑定com.package.my.login.domain.LogInAuthenticator注入位置com.package.my.login.repository.LoginDepository(logInAuthenticator)com.package.my.login.repository.Loginpository注入位置com.package.my.login.domain.LoginUserCase(loginDepository)com.package.my.login.domain.LoginUserCase注入于com.package.my.login.presentation.LoginViewModel(loginUserCase)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(工厂)androidx.lifecycle.ViewModelProvider.Factory注入于com.package.my.login.ui.SignUpActivity.viewModelFactorycom.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提供的。

这是否意味着,即使我真的不需要在SignUpActivity中创建GoogleSignInClient,唯一的解决方案也是添加LogInModule

您已经声明两个@ContributesAndroidInjector方法都依赖于ViewModelModule。在ViewModelModule中,您已经声明了所有的ViewModels,这意味着,当Dagger想要为SignUpActivity构建依赖树时,还需要明确地提到应该如何构建LoginViewModel。这是因为Dagger需要知道ViewModelModule内部声明的每个依赖项应该如何构造。

对于您的情况,解决方案将是在所有@ContributesAndroidInjector声明中包含所有模块(这是一种丑陋的方法),,或者,将SignUpViewModel的provider方法移动到SignUpModule,而不为SignUpActivity声明包含ViewModelModule

这是适用于我的设置。

首先,我创建了一个BaseActivityModule,所有功能模块都应该将其包含在其专用的@Module类中:


@Module
abstract class BaseActivityModule {
@Binds abstract fun bindsViewModelFactory(factory: MyViewModelFactory): ViewModelProvider.Factory
}

然后,假设我们有两个功能:FooBar


@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

FooModuleBarModule声明为:


@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

为什么需要添加LoginModule的答案在错误日志中。错误日志跟踪依赖关系如下:

注册活动&lt--在-->ViewModelFactory注入&lt--在-->LoginViewModel中注入&lt--在-->LoginUserCase&lt--在-->LoginPositional&lt--在-->LoginAuthenticator处注入

上面的映射显示您需要添加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

相关内容

  • 没有找到相关文章

最新更新