好吧,正如我试图在标题中总结的那样,这是详细信息。
我们有一个相对较大的应用程序,它实际上不是理想的方式使用Dagger,因此我们决定开始编写测试,为此,我需要公开Mockito的依赖项,因此,我面临着开始提供视图模型的问题。使用单例工厂,仍然适用,周围有大量的教程对此进行了解释。
我们在我们的应用程序中拥有许多功能,这些功能是使用单个活动和导航组件实现的,该单个活动有时具有创建的视图模型,可用于在容器活动和使用填充的片段之间共享数据导航编辑器。
我不知道以下内容,我如何使用匕首注入共享的视图模型,每次为特定视图模型调用@Inject
时都返回相同的实例,我知道可以通过以下方法完成范围,但我无法弄清楚,我有一个解释,我需要验证。 (我将在下面提供我的代码)
我首先实现了Singleton ViewModelFactory,如下所示:
@Singleton
class ViewModelFactory @Inject constructor(private val creators: Map<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")
try {
@Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
然后我创建了如下的ViewModelModule,它提供了ViewModelFactory和ViewModel:
@Module
abstract class ViewModelFactoryModule {
@Binds
abstract fun bindsViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory
@Binds
@IntoMap
@EbMainScope
@ViewModelKey(EBMainContainerViewModel::class)
abstract fun bindEbMainViewModel(ebMainContainerViewModel: EBMainContainerViewModel): ViewModel
}
在您询问之前,这是作用域的实现:
@Scope
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
annotation class EbMainScope
最后一步,这是我的活动/碎片注射器模块:
@Module
abstract class ScreensBuildersModule {
@ContributesAndroidInjector
@EbMainScope
abstract fun contributeEbMainActivity(): EBMainActivity
@ContributesAndroidInjector
@EbMainScope
abstract fun contributeEbDashboardMainFragment(): EBDashboardMainFragment
}
当然,我在AppComponent中进行了所有连接,尽管有我定义的范围,但应用程序运行顺利,有个问题,EbMainContainerViewModel
有两个实例。
我的解释是,我实际上有两个不同的提供者而不是一个,但是我仍然不明白为什么,因为我将其标记为@Singleton
。
有人对此有解释吗?如果需要更多输入,请告诉我。
然后,这是我设法完成的实用指南,我认为是可行的解决方案,并且由于@ pratz9999要求提供解决方案,所以这里是:
为了实例化ViewModel,您将需要一个ViewModelProvider,如果您依赖于上述实现,它会在后台创建一个ViewModelFactory,对于模块中的每个条目(即@IntoMap调用),都将实例化一个新的provider (可以),但是这里有个问题,它每次都会创建一个新的ViewModelFactory,请看以下内容:
/**
* Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given
* {@code fragment} is alive. More detailed explanation is in {@link ViewModel}.
* <p>
* It uses the given {@link Factory} to instantiate new ViewModels.
*
* @param fragment a fragment, in whose scope ViewModels should be retained
* @param factory a {@code Factory} to instantiate new ViewModels
* @return a ViewModelProvider instance
*/
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) {
Application application = checkApplication(checkActivity(fragment));
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(fragment.getViewModelStore(), factory);
}
我在做过一些研究后猜测的错误是,我没有注入适当的ViewModelFactory,所以我最终做了以下事情:
/**
* Factory for injecting view models
*/
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
fun <T: ViewModel> BaseNavFragmentWithDagger.getSharedViewModelWithParams(clazz: Class<T>): T =
activity?.run { ViewModelProviders.of(this, viewModelFactory).get(clazz) }
?: throw RuntimeException("You called the view model too early")
fun <T: ViewModel> BaseNavFragmentWithDagger.getPrivateViewModelWithParams(clazz: Class<T>): T =
ViewModelProviders.of(this, viewModelFactory).get(clazz)