当前位置: 首页 > 知识库问答 >
问题:

在不同的活动中注入ViewModelFactory

林修真
2023-03-14

我使用了著名的Dagger ViewModelFactory模式,以便能够为所有活动中的所有视图模型注入工厂。

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

我遇到的问题是,当我将工厂注入到活动匕首时失败了,因为我不打算使用的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。包裹我的AppComponent。java:8:错误:[Dagger/MissingBinding]com。包裹我的登录。领域如果没有@Provides注释方法,则无法提供LogInAuthenticator。公共抽象接口AppComponent扩展了dagger。AndroidAndroidInjector{^组件中存在具有匹配键的绑定:com.package.my.di.ActivityInjectorsModule\u LoginActivityInjector$app\u prodDebug.LoginActivitySubcomponent com.package.my.login.domain.LogInAuthenticator被注入com.package.my.login.repository.LoginRepository(LogInAuthenticator)通用域名格式。包裹我的登录。存储库。LoginRepository在com中注入。包裹我的登录。领域LoginUseCase(loginRepository)com。包裹我的登录。领域LoginUseCase在com中注入。包裹我的登录。演示LoginViewModel(loginUseCase)com。包裹我的登录。演示LoginViewModel在com中注入。包裹我的di。ViewModelModule。provideLoginViewModel(viewModel)java。util。地图,javax。注射供应商

这是因为LogInAuthenticator是由LogInModule提供的。

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

共有2个答案

孟楷
2023-03-14

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

注册活动

上面的地图显示,您需要添加LoginModule,因为Dagger需要它来成功地将ViewModelFactory注入到您的SignUpActivity中。

编辑

从您的视图模型模块中移动绑定,并将其放置在您的注册模块中,如下所示:

@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
易祖鹤
2023-03-14

您已经声明这两种方法都依赖于视图模型模块。在ViewModelModule中,您已经声明了所有的ViewModels,这意味着,在Dagger想要为SignUpActivity构建依赖树时,它还需要您明确提及应该如何构建LoginViewModel。这是因为Dagger需要知道如何构造ViewModelModule中声明的每个依赖项。

对于您的案例,解决方案是要么在所有声明中包含所有模块(这是一种丑陋的方法),要么将SignUpViewModel的provider方法移动到SignUpModule,并且不包含SignUpActivity声明的viewmodel。

这是适合我的设置。

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


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

然后,假设我们有2个功能:Foo和Bar:


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

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

ViewModelProvider. Factory的实现类应使用@PerActive限定范围,因为每次需要在特定活动范围内注入依赖项时,都应提供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
}

然后,我们在AppComponent中包括活动模块,如下所示:



@Singleton
@Component(modules = [
  AndroidInjectionModule::class,
  ActivitiesModule::class
])
interface AppComponent {
    ...
}

使用这种方法,我们将ViewModelProvider. Factory创建向下移动了一层:以前它位于最顶层的AppComponent中,现在每个子组件都将负责创建ViewModelProvider. Factory

 类似资料:
  • 如何在扩展Activity而不是AppCompatActivity的类中注入改装实例?这是模块: 这是我的主要活动: 这导致 用@AndroidEntryPoint注释的活动必须是 androidx.activity.ComponentActivity 的子类。(例如 FragmentActivity、AppCompatActivity 等)[剑柄]处理未完成。有关详细信息,请参阅上面的错误。 我

  • 在本活动中,我希望拥有导航抽屉,因此我扩展了'NavigationDrawer',在其他一些活动中,我希望使用相同的导航抽屉

  • 我正在使用以下项目 https://github.com/akotoe/android-slide-out-menu.git开发滑出菜单应用程序。 如何通过单击幻灯片菜单中的列表在同一视图中运行不同的活动。 例如,如果我单击项目1,我想在一个单独的活动中解析一个XML文件,并将该活动作为子项添加到此父视图中。因为在每一项单击上,我希望解析一个单独的XML文件,并且我希望在一个单独的布局文件中表示解

  • 我有两项活动。我想用第一个活动(如)读卡,第二个活动写卡。因为在发现卡时,活动需要处于活动状态。因此,我对这两项活动都使用了以下设置: 然而,我的问题是,当我在第二个活动中扫描NFC卡时,手机会显示第一个和第二个活动的意向选择器。 那么,当我在第二个活动(反之亦然)中时,如何通过代码禁用第一个活动的intent过滤器? 这是完整的AndroidManifest文件:

  • 问题内容: 示例ViewModel: 主要活动: 我想调用第二个活动并使MainActivity接收更改。那可能吗? 问题答案: 调用时,您实际上创建/保留了绑定到的,因此不同的Activity具有不同的特性,并且每个Activity 使用给定的工厂创建a的不同实例,因此您不能在不同的s中具有相同的a实例。 但是,您可以通过传递自定义ViewModel工厂的单个实例(充当单例工厂)来实现此目的,因

  • 我有这个问题,我不确定这是否是“预期”行为,但我的问题是: 我有一个Http筛选器: UserInfo和ActivationInfo都是@SessionScope,如下所示: 和 当我尝试访问调用过滤器的页面时,我在控制台上看到以下内容: 如果我转到不同的浏览器并输入“坏”校验码,用户信息/激活信息永远不会重新注入。IE,与不同的会话,我没有看到一个新的UserInfo/ActivationInf