在不同的活动中注入ViewModelFactory

时间:2019-11-09 20:07:02

标签: android dependency-injection viewmodel dagger-2 dagger

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

发生这种情况是因为LogInAuthenticatorLogInModule提供。

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

2 个答案:

答案 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