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

Lui*_*reu 2 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
}
Run Code Online (Sandbox Code Playgroud)

ViewModelKey 是一个简单的辅助注释类,如下所示:

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

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>()
}
Run Code Online (Sandbox Code Playgroud)

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

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

    @ActivityScoped
    @ContributesAndroidInjector
    internal abstract fun showDetailsActivity(): ShowDetailsActivity
}
Run Code Online (Sandbox Code Playgroud)

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

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

并且,如您所料,viewModelFactory作为字段注入:

@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
Run Code Online (Sandbox Code Playgroud)

两个视图模型都有外部依赖项,这些依赖项设置在顶级应用程序组件引用的其他模块上。

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

@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
Run Code Online (Sandbox Code Playgroud)

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

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

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

}
Run Code Online (Sandbox Code Playgroud)

之后, 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
}
Run Code Online (Sandbox Code Playgroud)

我已经更新了,ActivityBindingModule所以看起来像这样:

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

    @ActivityScoped
    @ContributesAndroidInjector(modules = [DetailsModule::class])
    internal abstract fun showDetailsActivity(): ShowDetailsActivity
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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.

Dav*_*jak 5

问题在于ViewModelFactory存在@Singleton并且它不会获得您在子组件中添加的任何绑定。从您的工厂中删除范围或使其成为@ActivityScoped(与活动的 ViewModel 范围相同)

Activity ( @ActivityScoped) 有权访问工厂 ( @Singleton),但工厂 ( @Singleton) 无权使用或创建来自较低范围 ( @ActivityScoped)的 ViewModel 。因此,将工厂移动到相同的范围 ( @ActivityScoped) 将使其可以访问创建有问题的视图模型。