android - 在不同的 Activity 中注入(inject) ViewModelFactory

标签 android dependency-injection viewmodel dagger-2 dagger

我使用著名的 Dagger-ViewModelFactory 模式来为所有 Activity 中的所有 ViewModel 注入(inject)工厂。

@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
    }
}

我遇到的问题是,当我将工厂注入(inject) 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,因为我不需要该模块中的绑定(bind)。 结果是我收到错误

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

最佳答案

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

针对您的情况的解决方案是包含所有 @ContributesAndroidInjector 声明中的所有模块(这是一种丑陋的方法),,或者,将将 SignUpViewModel 的提供程序方法转换为 SignUpModule,并且不包含用于 SignUpActivity 声明的 ViewModelModule

这是适合我的设置。

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


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

Then, assuming we have 2 features: Foo and Bar:


@Module
abstract class ActivitiesModule {
  @PerActivity @ContributesAndroidInjector(modules = [FooModule::class])
  abstract fun contributesFooActivity(): FooActivity

  @PerActivity @ContributesAndroidInjector(modules = [BarModule::class])
  abstract fun contributesBarActivity(): BarActivity
}

The implementation class of ViewModelProvider.Factory should be scoped with @PerActivity because the same instance of ViewModelProvider.Factory should be provided each time that dependency is needed to be injected in the scope of particular 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

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

关于android - 在不同的 Activity 中注入(inject) ViewModelFactory,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58783086/

相关文章:

java - 网络服务+安卓

android - 检查光标是否包含行结果?

dependency-injection - 在 Dagger 中使用不带 @Provides 的 @Singleton

asp.net-mvc - Linq to View 错误,传递通用列表 : Required IEnumerable

wpf - MVVM 和 VM 集合

c# - Automapper:如何不重复从复杂类型到基类的映射配置

Android:启动带有动画的应用程序

android - Couchbase Lite 到 CouchDB 的复制——没有数据

c# - Unity.MVC 无法使用参数解析我的 Controller 构造函数

dependency-injection - 从 ASP.NET Core 依赖注入(inject)中获取 ClaimsPrincipal 或 ClaimsIdentity