使用 Dagger 跨两个/或多个片段和活动共享 ViewModel

Oma*_*tom 3 android dagger-2

好吧,正如我试图在标题中总结的那样,这里是细节。

我们有一个相对较大的应用程序,它以非常不理想的方式使用 Dagger,因此我们决定开始编写测试,为此,我需要公开 Mockito 的依赖项,因此,我面临一个问题,即开始使用单例工厂,仍然适用,并且有大量教程可以解释这一点。

我们在我们的应用程序中拥有许多使用单个活动和导航组件实现的功能,该单个活动有时会创建一个视图模型,用于在容器活动和使用导航填充的片段之间共享数据编辑。

我想不通的是,如何使用 dagger 注入共享视图模型,每次调用@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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我创建了我的 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

}
Run Code Online (Sandbox Code Playgroud)

在你问之前,这里是范围实现:

@Scope
@Target(
        AnnotationTarget.FUNCTION,
        AnnotationTarget.PROPERTY_GETTER,
        AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
annotation class EbMainScope
Run Code Online (Sandbox Code Playgroud)

最后一步,这是我的活动/片段注入器模块:

@Module
abstract class ScreensBuildersModule {

    @ContributesAndroidInjector
    @EbMainScope
    abstract fun contributeEbMainActivity(): EBMainActivity

    @ContributesAndroidInjector
    @EbMainScope
    abstract fun contributeEbDashboardMainFragment(): EBDashboardMainFragment

}
Run Code Online (Sandbox Code Playgroud)

当然,我在 AppComponent 中连接了所有内容,并且应用程序运行顺利EbMainContainerViewModel,尽管我定义了范围,但仍有两个 实例。

我的解释是,我实际上有两个不同的提供者而不是一个,但我仍然不明白为什么,因为我将其标记为@Singleton.

有人对此有解释吗?如果需要更多输入,请让我知道伙计们。

Ale*_* F. 6

我遇到了同样的问题,但可以这样解决:

  1. 例如,我使用此代码:https : //github.com/android/architecture-samples/tree/dagger-android
  2. 在我的片段(我想在其中使用共享视图模型)中,我使用了这个(它帮助了我):

    private val viewModel by viewModels<SearchViewModel>({ activity as MainActivity }) { viewModelFactory }
    
    Run Code Online (Sandbox Code Playgroud)

    而不是这个(如示例中所示):

    private val viewModel by viewModels<SearchViewModel> { viewModelFactory }
    
    Run Code Online (Sandbox Code Playgroud)

因为第一个参数是ownerProducer,所以我们在活动范围内创建一个 ViewModel 。


Oma*_*tom 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);
}
Run Code Online (Sandbox Code Playgroud)

经过一番研究后我猜测是我的错,我没有注入正确的 ViewModelFactory,所以我最终做了以下操作:

  • 在我的基片段类中,我注入了一个 ViewModelFactory,如下所示:
/**
* Factory for injecting view models
*/
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
Run Code Online (Sandbox Code Playgroud)
  • 然后在实用程序类中,我有一个返回共享 ViewModel 的方法,如下所示(注意 Activity?.run 这使得视图模型实例绑定到持有活动,从而具有共享范围概念):
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")
Run Code Online (Sandbox Code Playgroud)
  • 最后,对于私有 ViewModel,我采用了以下方法:
fun <T: ViewModel> BaseNavFragmentWithDagger.getPrivateViewModelWithParams(clazz: Class<T>): T =
        ViewModelProviders.of(this, viewModelFactory).get(clazz)
Run Code Online (Sandbox Code Playgroud)