好吧,正如我试图在标题中总结的那样,这是详细信息。
我们有一个相对较大的应用程序,它实际上不是理想的方式使用Dagger,因此我们决定开始编写测试,为此,我需要公开Mockito的依赖项,因此,我面临着开始提供视图的问题使用Singleton工厂的模型仍然适用,并且有大量的教程对此进行了解释。
我们在应用程序中拥有许多功能,这些功能是使用单个活动和导航组件实现的,该单个活动有时会创建一个视图模型,用于在容器活动和填充的片段之间共享数据使用导航编辑器。
我不知道的是以下内容,我如何使用匕首注入共享的视图模型,每次为特定视图模型调用n0 - j
时都返回相同的实例,我知道它可以可以通过作用域来完成,但是我无法弄清楚,我有一个解释,我需要进行验证。 (我将在下面提供我的代码)
我首先实现了Singleton ViewModelFactory,如下所示:
@Inject
然后,我创建了ViewModelModule,它提供了ViewModelFactory和ViewModel,如下所示:
@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)
}
}
}
在您询问之前,这是作用域的实现:
@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
当然,我将所有内容都连接到AppComponent中,尽管我定义了范围,但该应用程序运行平稳,有两个@Module
abstract class ScreensBuildersModule {
@ContributesAndroidInjector
@EbMainScope
abstract fun contributeEbMainActivity(): EBMainActivity
@ContributesAndroidInjector
@EbMainScope
abstract fun contributeEbDashboardMainFragment(): EBDashboardMainFragment
}
实例。
我的解释是,我实际上有两个不同的提供程序而不是一个,但是我仍然不明白为什么,因为我将其标记为EbMainContainerViewModel
。
有人对此有解释吗?如果需要更多输入,请告诉我。
答案 0 :(得分:4)
我遇到了同样的问题,但是可以通过以下方式解决:
在我的片段(我想使用Shared ViewModel)中,我使用 这(对我有帮助):
private val viewModel by viewModels<SearchViewModel>({ activity as MainActivity }) { viewModelFactory }
代替此示例(如示例):
private val viewModel by viewModels<SearchViewModel> { viewModelFactory }
因为第一个参数是 ownerProducer ,所以我们在活动范围中创建了一个ViewModel。
答案 1 :(得分:0)
那么,好的,这是我设法做到的实用指南,我认为是可行的解决方案,由于@ pratz9999要求提供解决方案,所以它是:
为了实例化ViewModel,您将需要一个ViewModelProvider,如果您依赖于上述实现,则将在其内部创建一个ViewModelFactory,对于模块中的每个条目(即@IntoMap调用),将提供一个新的提供者。实例化(很好),但要注意的是,每次都会创建一个新的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)