dagger2和android:加载模块,将视图模型注入地图

时间:2018-09-15 20:53:47

标签: android dagger-2

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

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

@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是一个简单的帮助程序注释类,如下所示:

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

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

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

这是ActivityBindingModule的代码,它负责设置子组件(在这种情况下,是我的应用程序使用的活动):

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

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

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

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

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

@Inject lateinit var viewModelFactory: ViewModelProvider.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

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

为了对此进行测试,我首先将ShowDetailsViewModel移到一个只有一个条目的新模块中:

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

}

之后,ViewModelModule如下所示:

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

我已经更新了ActivityBindingModule,因此看起来像这样:

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

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

请注意,现在我将DetailsModule(实例化ShowDetailsViewModel)传递给ContributeAndroidInjector注释,该注释应用于showDetailsActivity方法,因为该视图模型是仅由该活动使用。

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

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

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

我认为,将每个视图模型的声明移动到子组件使用的每个模块中,仍应允许其在由顶部组件注册的模块注入的映射中注册。我错了吗?使我无法进行这项工作是什么?

谢谢。

1 个答案:

答案 0 :(得分:2)

问题出在ViewModelFactory@Singleton的问题上,它不会获得您在子组件中添加的任何绑定。从工厂中删除范围或使其为@ActivityScoped(与Activity的ViewModel相同)

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