将ViewModelFactory注入不同的活动中

问题描述 投票:0回答:2

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

这是因为LogInAuthenticatorLogInModule提供。

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

android dependency-injection viewmodel dagger-2 dagger
2个回答
1
投票

您已经声明@ContributesAndroidInjector的两个方法都依赖于ViewModelModule。在ViewModelModule内部,您已经声明了所有ViewModels,这意味着,当Dagger想要为SignUpActivity构建依赖关系树时,它还需要you明确提及LoginViewModel ]应该被构造。发生这种情况是因为Dagger需要知道应该如何构造在ViewModelModule内部声明的每个依赖项。

适合您的解决方案是将所有模块都包含在所有@ContributesAndroidInjector声明中(这是一个丑陋的方法),,或者将SignUpViewModel的提供者方法移至SignUpModule并且在ViewModelModule声明中不包含SignUpActivity

这是对我有用的设置。

首先,我创建了一个BaseActivityModule,所有功能模块均应在其专用的@Module类中include


@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

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


0
投票

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

SignUpActivity ViewModelFactory 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
© www.soinside.com 2019 - 2024. All rights reserved.