我已经开始使用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中注册的。
我认为,将每个视图模型的声明移动到子组件使用的每个模块中,仍应允许其在由顶部组件注册的模块注入的映射中注册。我错了吗?使我无法进行这项工作是什么?
谢谢。
答案 0 :(得分:2)
问题出在ViewModelFactory
是@Singleton
的问题上,它不会获得您在子组件中添加的任何绑定。从工厂中删除范围或使其为@ActivityScoped
(与Activity的ViewModel相同)
活动(@ActivityScoped
)有权访问工厂(@Singleton
),但是工厂(@Singleton
)无权使用或从较低范围创建ViewModel( @ActivityScoped
)。因此,将工厂移到相同的范围(@ActivityScoped
)可以授予工厂创建相关视图模型的权限。