【问题标题】:Injecting ViewModelFactory in different activities在不同的活动中注入 ViewModelFactory
【发布时间】:2020-03-06 01:29:06
【问题描述】:

我正在使用著名的 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: error: [Dagger/MissingBinding] com.package.my.login.domain.LogInAuthenticator 不能在没有 @Provides 注释的方法的情况下提供。 公共抽象接口 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(工厂) 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,即使我真的不需要在SignUpActivity 中创建GoogleSignInClient

【问题讨论】:

  • 请始终包含错误消息的完整“堆栈跟踪”及其周围的代码。我相信问题是由于将LoginViewModel 添加到注册组件而出现的。通常您注册一次ViewModelFactory(例如您的 AppComponent)然后将视图模型绑定添加到您需要它们的组件中,因此只需将登录虚拟机添加到登录组件,将注册虚拟机添加到注册组件等
  • @DavidMedenjak 我没有将 LoginViewModel 添加到 SignUp 子组件中。如果我这样做,那么它会起作用,但我不想这样做
  • 我没有添加整个错误,因为我认为问题很清楚。解决方案不是。另请注意,我也创建了一次工厂。问题是我需要在所有子组件中添加所有依赖项,而不仅仅是每个特定子组件的必要依赖项。
  • 我现在添加了整个错误

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


【解决方案1】:

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

您的情况的解决方案是在所有 @ContributesAndroidInjector 声明中包含所有模块(这是一种丑陋的方法),,或者,移动 SignUpViewModel 的提供程序方法到SignUpModule 并且不包括ViewModelModule 用于SignUpActivity 声明。

这是适合我的设置。

首先,我创建了一个BaseActivityModule,所有功能模块都应包含它们专用的@Module类:

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

那么,假设我们有 2 个特征: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

【讨论】:

  • 但是我需要包含工厂并且构建工厂匕首需要注入所有视图模型的所有创建者的地图,所以无论如何都会存在依赖关系
  • *所有视图模型
【解决方案2】:

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

SignUpActivity ViewModelFactory LoginViewModel LoginUseCase LoginRepository 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

【讨论】:

  • 没错,但问题是如何避免 SignUpActivity 依赖于 LoginModule:我实际上并没有在 SignUpActivity 中使用 LoginModule
  • 这也是@azizbekian 的建议。它不起作用,因为工厂仍然尝试访问所有 ViewModel
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多