当前位置: 首页 > 知识库问答 >
问题:

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

巫懿轩
2023-03-14

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

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

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

我无法理解的是,我如何使用dagger注入共享视图模型,每次调用特定视图模型时返回相同的实例,我知道可能可以通过作用域来完成,但我无法理解,我有一个解释需要验证。(我将在下面提供我的代码)

我首先实现了我的单例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中连接了所有内容,应用程序运行平稳,尽管我定义了范围,但仍然有两个实例。

我的解释是,实际上我有两个不同的提供者,而不是一个,但我仍然无法理解为什么,因为我将其标记为单例。

有人对此有解释吗?如果需要更多信息,请告诉我。

共有2个答案

邵正雅
2023-03-14

好吧,这是我设法做的实用指南,我想这是一个可行的解决方案,既然@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,所以我结束了以下操作:

  • 在我的基本片段类中,我注入了ViewModelFactory,如下所示:
/**
* Factory for injecting view models
*/
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
  • 然后在一个实用程序类中,我有一个返回共享ViewModel的方法,如下所示(注意活动?. 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")
  • 最后,对于私有ViewModels,我选择了这个:
fun <T: ViewModel> BaseNavFragmentWithDagger.getPrivateViewModelWithParams(clazz: Class<T>): T =
        ViewModelProviders.of(this, viewModelFactory).get(clazz)
祁霖
2023-03-14

我也有同样的问题,但要这样解决:

>

  • 例如,我使用以下代码:https://github.com/android/architecture-samples/tree/dagger-android
  • 在我的片段中(我想在其中使用Shared ViewModel),我使用了这个(它帮助了我):

    private val viewModel by viewModels<SearchViewModel>({ activity as MainActivity }) { viewModelFactory }
    

    而不是这样(如在示例中):

    private val viewModel by viewModels<SearchViewModel> { viewModelFactory }
    

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

  •  类似资料:
    • 在我的应用程序中,我使用了一个活动和两个片段。该应用程序使用带有容器的布局,因此片段是通过事务添加的。第一个片段包含列表视图,另一个片段包含列表视图项的详细视图。两个片段都使用setRetainInstance(true)。片段是通过替换事务添加的,并设置了addToBackStack(null)。列表片段包含一个实例变量,其中包含列表的一些信息。现在我正在切换到详细并按回,实例变量为null。我

    • 我读过很多关于这方面的文章,但也有2012年或更早的文章。 (我只是打算从数据库中读取和插入一些数据。)

    • 我想在Android的一个活动中添加两个片段。但在加法时,它给出了错误; activity_main.xml 片段的布局1 片段2的布局

    • 我有一个疑问,想澄清一些关于包含多个片段的活动的观点。 我有10个片段与一个活动(HomeActivity.java)相连;此活动包含一个导航抽屉和工具栏,带有多个图标,如搜索、添加、删除、后退按钮等。 我遵循的结构如下:, 用户点击抽屉菜单中的任何项目,我正在加载片段, 碎片加载- 我正在根据HomeActivity本身中的片段更改标题名称,基于工具栏。 我的导航抽屉项目只有一个片段,在frag

    • 问题内容: 我在小组活动中有一个片段,我想用另一个片段替换它: 在不使用活动组的情况下作为单独的项目完成时,它工作正常,当控件进入getview()时,每件事在日志猫中都可以正常工作,但是没有视图可见,甚至没有任何异常出现,我希望将书详细信息片段由部分详细信息片段代替。 图书详细信息片段的XML具有id book_description_fragment,而部分描述片段的xml具有id secti

    • 我有一个字符串,用户将编辑该字符串,并在使用应用程序时显示给他。他可以随时编辑字符串。我很熟悉SQLite数据库,但因为出于这个目的,我只使用一个字符串/一条记录,所以我觉得SharedPreferences会更好。然而,在遵循了两个不同的教程之后,我无法获得它,所以保存数据。在这两种情况下,我都需要修改教程代码,因为我将使用两个活动,一个用于查看代码,另一个用于编辑代码。我找不到在两个活动中使用