dagger2和android:加载模块,将视图模型注入地图 [英] dagger2 and android: load module which injects viewmodel on a map

查看:61
本文介绍了dagger2和android:加载模块,将视图模型注入地图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经开始使用Dagger2,所以还有很多东西要学习。我想知道是否有人可以指出正确的方向。



因此,我创建了一个模块,用于注册活动所使用的视图模型。看起来像这样:

  @Module 
抽象类ViewModelModule {
@Binds
内部抽象乐趣bindViewModelFactory(factory:ViewModelFactory):ViewModelProvider.Factory

@Binds
@IntoMap
@ViewModelKey(MainActivityViewModel :: class)
内部抽象乐趣bindMainActivityViewModel(viewModel :MainActivityViewModel):ViewModel

@Binds
@IntoMap
@ViewModelKey(ShowDetailsViewModel :: class)
abstract fun bindShowDetaislViewModel(viewModel:ShowDetailsViewModel):ViewModel
}

ViewModelKey 是一个简单的帮助器注释看起来像这样的类:

  @Target(AnnotationTarget.FUNCTION,AnnotationTarget.PROPERTY_GETTER,AnnotationTarget.PROPERTY_SETTER)
@ Retention(AnnotationRetention.RUNTIME)
@MapKey
注释类ViewModelKey(值:KClass< out ViewModel>){
}

ViewModelModule 由我的主要应用程序组件加载(用于创建应用程序):

  @Singleton 
@Component(
modules = [
AndroidSupportInjectionModule :: class ,
AppModule :: class,
DatabaseModule :: class,
NewsServiceModule :: class,
JobBindingModule :: class,
ViewModelModule :: class,
PreferencesModule :: class,
ActivityBindingModule :: class
]

接口AppComponent:AndroidInjector< MyApp> {
@ Component.Builder
抽象类生成器:AndroidInjector.Builder< MyApp>()
}

这是 ActivityBindingModule 的代码,负责设置子组件(在本例中为我的应用程序使用的活动):

  @Module 
抽象类ActivityBindingModule {
@ActivityScoped
@ContributesAndroidInjector()
内部抽象乐趣mainActivity():MainActivity

@ActivityScoped
@ContributesAndroidInjector
内部抽象乐趣showDetailsActivity():ShowDetailsActivity
}

内部,每个活动都使用如下代码实例化视图模型(从 onCreate 方法):

  //视图模型代码
_viewModel = ViewModelProviders.of(this,viewModelFactory) [ShowDetailsViewModel :: class.java]

并且,正如您所期望的, viewModelFactory 作为字段被注入:

  @Inject lateinit var viewModelFactory:ViewModelProvider.Factory 

两个视图模型都具有设置的外部依赖项



为了完整起见,这是我的视图模型工厂的代码:

  @Singleton 
类ViewModelFactory @Inject构造函数(私有val viewModels:MutableMap< Class< out ViewModel> ;, Provider< ViewModel>>): ViewModelProvider.Factory {
覆盖fun< T:ViewModel?> create(modelClass:Class< T>):T
= viewModels [modelClass] ?. get()as T

此代码有效,但似乎可以改进。阅读文档后,我的印象是我可以重构我的 ViewModeModule ,以便它可以简单地实例化 ViewModelFactory ,然后将每个视图模型声明移动到单独的模块中(以便每个视图模型声明只能在正确活动中注入)。



为了测试这一点,我首先将 ShowDetailsViewModel 移到一个只有一个模块的新模块中。条目:

  @Module 
内部抽象类DetailsModule {
@Binds
@IntoMap
@ViewModelKey(ShowDetailsViewModel :: class)
抽象乐趣bindShowDetaislViewModel(viewModel:ShowDetailsViewModel):ViewModel

}

之后,ViewModelModule如下所示:

  @Module 
抽象类ViewModelModule {
@Binds
内部抽象乐趣bindViewModelFactory(factory:ViewModelFactory):ViewModelProvider.Factory

@ Binds
@IntoMap
@ ViewModelKey(MainActivityViewModel :: class)
内部抽象乐趣bindMainActivityViewModel(viewModel:MainActivityViewModel):ViewModel
}

我已经更新了 ActivityBindingModule ,以便在这样的确定:

  @Module 
抽象类ActivityBindingModule {
@ActivityScoped
@ContributesAndroidInjector ()
内部抽象乐趣mainActivity():MainActivity

@ActivityScoped
@ContributesAndroidInjector(modules = [DetailsModule :: class])
内部抽象乐趣showDetailsActivity() :ShowDetailsActivity
}

请注意,现在我通过了 DetailsModule (实例化 ShowDetailsViewModel )到 ContributeAndroidInjector 注释,该注释应用于 showDetailsActivity 方法,因为该视图模型仅由该活动使用。



现在,我肯定会丢失一些东西,因为这样做,总是会出现以下异常:

  java.lang.IllegalStateException:ViewModelProviders.of(th ... ilsViewModel: :class.java]不能为空

如果我调试了该应用程序,则可以看到将ShowDetailsViewModel移入其自己的模型不会在工厂使用的地图上注册它(即,该地图只有一个条目,对应于到注册在ViewModelModule中的 MainActivityViewModel



我认为将每个视图模型的声明移动到每个子组件使用的模块仍应允许将其注册在由顶部组件注册的模块注入的映射中。我错了吗?

谢谢。

解决方案 div>

问题在于 ViewModelFactory @Singleton ,并且无法获得任何绑定您添加子组件。从您的工厂中删除范围或将其设为 @ActivityScoped (与活动的ViewModel相同)



活动( @ActivityScoped )可以访问工厂( @Singleton ),但是工厂( @Singleton )无权使用或在较低范围( @ActivityScoped )中创建ViewModel。因此,将工厂移到相同的作用域( @ActivityScoped )将使它具有访问权限以创建相关的视图模型。


I've started using Dagger2, so there's still a lot to learn. I'm wondering if someone could point me on the right direction.

So, I've created a module for registering the view models used by my activities. It looks like this:

@Module
abstract class ViewModelModule {
    @Binds
    internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory

    @Binds
    @IntoMap
    @ViewModelKey(MainActivityViewModel::class)
    internal abstract fun bindMainActivityViewModel(viewModel: MainActivityViewModel): ViewModel

    @Binds
    @IntoMap
    @ViewModelKey(ShowDetailsViewModel::class)
    abstract fun bindShowDetaislViewModel(viewModel: ShowDetailsViewModel): ViewModel
}

ViewModelKey is a simple helper annotation class which looks like this:

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey (val value: KClass<out ViewModel>) {
}

The ViewModelModule is loaded by my main app component (used for creating the app):

@Singleton
@Component(
        modules=[
            AndroidSupportInjectionModule::class,
            AppModule::class,
            DatabaseModule::class,
            NewsServiceModule::class,
            JobBindingModule::class,
            ViewModelModule::class,
            PreferencesModule::class,
            ActivityBindingModule::class
        ]
)
interface AppComponent: AndroidInjector<MyApp> {
    @Component.Builder
    abstract class Builder: AndroidInjector.Builder<MyApp>()
}

And here's the code for the ActivityBindingModule, responsible for setting up the subcomponents (in this case, activities used by my app):

@Module
abstract class ActivityBindingModule {
    @ActivityScoped
    @ContributesAndroidInjector()
    internal abstract fun mainActivity(): MainActivity

    @ActivityScoped
    @ContributesAndroidInjector
    internal abstract fun showDetailsActivity(): ShowDetailsActivity
}

Internally, each activity is instantiating the view model with code that looks like this (called from within the onCreate method):

//view model code
_viewModel = ViewModelProviders.of(this, viewModelFactory)[ShowDetailsViewModel::class.java]

And, as you'd expect, viewModelFactory is injected as field:

@Inject lateinit var viewModelFactory: ViewModelProvider.Factory

Both view models have external dependencies which are set up on the other modules referenced by the top app component.

And, for the sake of completeness, here's the code for my view model factory:

@Singleton
class ViewModelFactory @Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T
        = viewModels[modelClass]?.get() as T

This code works, but it seems like it can be improved. After reading the docs, I'm under the impression that I could refactor my ViewModeModule so that it will simply instantiate my ViewModelFactory and move each of the view model declarations into separate module (so that each of them can be injected only in the "correct" activity).

In order to test this, I've started by moving the ShowDetailsViewModel into a new module which has only one entry:

@Module
internal abstract class DetailsModule {
    @Binds
    @IntoMap
    @ViewModelKey(ShowDetailsViewModel::class)
    abstract fun bindShowDetaislViewModel(viewModel: ShowDetailsViewModel): ViewModel

}

After that, the ViewModelModule looks like this:

@Module
abstract class ViewModelModule {
    @Binds
    internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory

    @Binds
    @IntoMap
    @ViewModelKey(MainActivityViewModel::class)
    internal abstract fun bindMainActivityViewModel(viewModel: MainActivityViewModel): ViewModel
}

And I've updated the ActivityBindingModule so that in looks like this:

@Module
abstract class ActivityBindingModule {
    @ActivityScoped
    @ContributesAndroidInjector()
    internal abstract fun mainActivity(): MainActivity

    @ActivityScoped
    @ContributesAndroidInjector(modules = [DetailsModule::class])
    internal abstract fun showDetailsActivity(): ShowDetailsActivity
}

Notice that now I'm passing the DetailsModule (which instantiates the ShowDetailsViewModel) to the ContributeAndroidInjector annotation which is applied to the showDetailsActivity method because that view model is only used by that activity.

Now, I'm surely missing something because after doing this, I'm always getting the following exception:

java.lang.IllegalStateException: ViewModelProviders.of(th…ilsViewModel::class.java] must not be null

If I debug the app, I can see that moving the ShowDetailsViewModel into its own model does not register it on the map used by the factory (ie, the map has only one entry, which corresponds to the MainActivityViewModel that is registered in the ViewModelModule.

I thought that moving each view model the declaration into each a module used by a subcomponent should still allow it to be registered in a map injected by a module which is registered with the top component. Am I wrong? What is it that I'm missing to make this work?

Thanks.

解决方案

The problem lies with ViewModelFactory being @Singleton and that it won't get any of the bindings you add in your subcomponents. Remove the scope from your factory or make it @ActivityScoped (the same scope as the ViewModel for the Activity)

The Activity (@ActivityScoped) has access to the factory (@Singleton), but the factory (@Singleton) does not have access to use or create the ViewModel from a lower scope (@ActivityScoped). So moving the factory to the same scope (@ActivityScoped) would give it access to create the viewmodel in question.

这篇关于dagger2和android:加载模块,将视图模型注入地图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆