使用Fragment的ViewModel而不是访问Activity的ViewModel?

31
问题很简单。问题是在使用ViewModels、LiveData和其他相关Lifecycle感知架构方法的情况下。
我有一个带有NavDrawer的Activity,它可以在内部切换片段。
同时,我有一个情况,即两个片段同时出现在屏幕上——这将是主要的痛点。 一个Fragment有一个带有嵌套Fragments不要问为什么)的ViewPager。 另一个片段只是在用户执行某些操作时从第一个片段获取信息。这是通过共享活动视图模型实现的。但是,应用程序本身具有大量的业务逻辑,并且随着进一步的发展,视图模型变得越来越大。
我想问的是,不是一个修复此问题的收据或规则,也不是通过修复整个项目结构来克服此问题。我想请教如何在android.arch.lifecycle风格中应用MVVM方法来解决我的用例。
我没有看到比在片段之间共享Activity ViewModel更复杂的东西。但是,这并不是治愈方法。 enter image description here
你能看到的是一团乱麻。问题在于所有人都共享ActivityViewModel。FirstFragment中的ViewPager连接(聚合)意味着在FirstFragment内部初始化ChildFragments,它们也使用相同的ActivityViewModel(救救我)。因此,每个人都在使用一个共享的ViewModel。
我的建议是为每个层添加一个ViewModel。这样,Activity / Fragment / ChildFragments就有了自己的ViewModel。 但是,这里出现了一个问题 - 那我们该如何进行通信呢
可能的解决方案:
  • 每个组件有两个ViewModel。一个ViewModel将处理/委托业务逻辑,另一个将进行通信。每个组件有两个ViewModel - 不太好,对吧?
  • 使用旧式接口(拜托别这样做!
  • 其他解决方法-例如DB / SharedPrefs / Realm更改侦听器和事件总线(我已经老了,做不来这个 :()。

  • 请在此处提供您的解决方案!

我认为以上所有方法都违反了许多设计原则,那么我应该怎么办呢? 我该如何摆脱这困境?有没有Uncle Bob或其他超级英雄来帮忙?

附言 - 嗯,创建UML或其他图表不是我的强项。对此抱歉。
再附言 - 我知道google samples


2
每个组件有多个ViewModel有什么问题?它们按类名键入是有原因的。 - ianhanniballake
@ianhanniballake 嗯,我对此有一些争议。我总是试图将一个组件绑定到一个虚拟机上。假设每个组件有一个虚拟机。https://github.com/googlesamples/android-architecture-components/issues/29 - 不一样,但每个屏幕都有一个视图模型。 - Yurii Tsap
当我有许多视图模型时,所有的通信都是通过流和更改共享管理器/模型中的变量进行的。例如,第一个片段将输入更改为6并将其设置在管理器(模型)中,管理器使用可观察模式(在您的情况下可能是MutableLiveData)通知观察者(观察实时数据变量的其他片段)该值已更改。 - V-master
1
@V-master 当然可以,但在这种情况下,我需要引用模型/管理器的参考。因此,在我的片段中,我将引用两个视图模型。 - Yurii Tsap
1
你的结论是什么? - Gilberto Ibarra
4个回答

18
我建议您可以为整个用例处理两个ViewModel。创建一个ViewModel,比如说MyActivityViewModel,来处理与activity级别相关的所有逻辑。因此,如果任何fragment逻辑直接涉及到您的activity,则可以像下面这样共享您的ViewModel:
ViewModelProviders.of(getActivity()).get(MyActivityViewModel.class); // Like this in fragment.

&

ViewModelProviders.of(this).get(MyActivityViewModel.class); // Like this in activity.

这将在您的活动片段之间共享通用ViewModel.


如果您需要在ChildFragment之间共享逻辑,则另一个ViewModel将适用于您的情况FirstFragment:

在这里,您可以像下面这样共享ViewModel,比如FragmentViewModel

ViewModelProviders.of(this).get(FragmentViewModel.class); // Like this in FirstFragment which is having view pager.

&

ViewModelProviders.of(getParentFragment()).get(FragmentViewModel.class); // Like this in View pager fragments, getParentFragment() is First fragment in our case.

虽然如此,我们仍然可以在FirstFragment的子片段中使用MyActivityViewModel的活动级别,例如:

ViewModelProviders.of(getActivity()).get(MyActivityViewModel.class);

7

首先,为一个 View 使用多个 ViewModel 是没有害处的。

我建议将 ViewModel 根据所获取和操作的 数据 类型进行分组,以一种自然的方式进行分组。

对于你的情况,如果片段和活动的逻辑非常相似,我认为你可以使用单个 ViewModel,但我会避免这样做。

我会将活动的 ViewModel 拆分成较小的部分,并在我的 Fragments 中重复使用适当的 ViewModel,以便我既不会有一个 God ViewModel,也不会在不同的 ViewModel 中出现大致相同的代码。


5

这是Jeel Vankhede所给答案的更新版本,并且也提供了相应的Kotlin实现。

由于ViewModelProviders已被弃用,现在我们需要使用ViewModelProvider。

以下是在Activity中的操作方法:

ViewModelProvider(this).get(MyActivityViewModel::class.java)

以下是在 Fragment 中如何执行 的方法:

ViewModelProvider(requireActivity()).get(MyActivityViewModel::class.java)

这就是我们都在寻找的答案:简单,有效。【掌声响起】 - SMBiggs
我遇到了麻烦。这是代码:mainCommandModel = new ViewModelProvider(requireActivity()).get(MainCommandModel.class); mainCommandModel.getActiveState().observe(getViewLifecycleOwner(), mainCommandObserver);这导致对mainCommandModel(即ViewModel)构造函数的第二次调用,因此具有此代码的片段无法观察到活动所做的更改,可能是因为活动也调用了该构造函数。有什么建议吗? - carl

2
为了解决FirstFragment与其子片段共享视图模型的问题,您可以使用以下代码从任何子片段访问FirstFragmentViewModel
    // in ChildFragment1
    val firstFragmentViewModel: FirstFragmentViewModel by viewModels(
        { requireParentFragment() }
    )


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接