如何生成具有临时子视图的组合视图模型?

How to generate composed view models with temporary sub views?

场景

我有一个测验生成器,它生成一系列不同 类 的测验。序列不限长

测验生成器有一个视图模型。每种类型的测验都有一个视图模型。测验生成器视图模型应根据 类.

创建测验的视图模型

问题

视图模型不得包含对生命周期的引用,但我需要生命周期来创建视图模型。

ViewModelProviders.of(lifecycle).get(classForQuizzType);

问题

在哪里创建测验的子视图模型?

我能想到的一种解决方案是每次从 activity 注入子视图模型。这是一个弯路,尤其是在涉及嵌套视图的情况下。

另一个解决方案是在嵌套视图中创建视图模型,这看起来也很丑陋,因为从视图内部访问生命周期并不常见。

如果没有干净的解决方案,我的架构方法有什么问题?这种场景应该用fragments吗?

pskink启发,我自己给出第一个答案。也许我会在对建议的方法进行一些体验后更新答案。

如果视图模型应生成子对象 aka 组件,只要主对象绑定到生命周期并且组件是从主对象引用的,则组件本身不需要绑定到生命周期。

对于给定的示例,这意味着创建主对象的好位置是 activity 的顶级位置,lifecycle 可直接在该位置使用。测验对象是从主对象引用的。它们不需要直接访问生命周期,可以在任何地方创建,例如在主对象内部。这使得可以按需创建它们。

组件可能是也可能不是 ViewModel 的子 class。我认为扩展 ViewModel 是一个很好的做法。这个父 class 引入了 onCleared 方法。这是从底层模型中删除观察者的地方。如果不这样做,您可能会造成内存泄漏。

您必须注意在正确的时刻调用 onCleared,至少从主对象的 onCleared 方法调用。在这种特殊情况下,必须在生成新测验之前清除每个先前的测验,以从基础测验模型中删除引用。

可以使用 new 关键字简单地创建组件的视图模型。无需使用工厂或提供程序。

If there is no clean solution, what's wrong with my approach of architecture? Should I use fragments for this kind of scenario?

是的,碎片是正确的选择

总结:

  1. LiveData 绑定 Views 没有真正的替代方案。
  2. 如果使用 LiveData,则需要 LifeCycle
  3. 如果序列中子视图的生命周期应该短于 activity 的生命周期,那么片段就是可行的方法。

详情

除了 LiveData 绑定 Views 之外没有真正的替代方法。

视图模型不应包含对视图的未终止引用,否则只要视图模型存在,视图就会存在,从而导致内存泄漏。有三种观察者模式来讨论视图如何观察视图模型。

a.) MutableLiveData

它们需要一个生命周期。当生命周期结束时,引用会自动清理。这是推荐的解决方案。

b.) 弱引用

理论上这应该可行。当对视图的最后一个硬引用消失时,垃圾收集器应该清理弱引用。在实践中,解决方案不稳定,引用有时会过早消失。

c.) 手工观察者

手工观察者必须调用移除方法。不幸的是,当视图消失时,没有定义的销毁钩子。视图中没有调用remove方法的地方。

因此 a.) 是根据我的经验唯一可能的解决方案。

由于 LiveData 片段需要生命周期

这里所说的子视图是按顺序创建的。如果我们将它们绑定到 activity,它们将堆积起来,直到 activity 消失,尽管它们只在一小段时间间隔内按顺序需要。

片段可以存在 activity 的一小部分时间。它们是将序列的子视图绑定到它们的正确解决方案。

示例代码

测验在这里被称为挑战。 FragmentManger 始终是 activity,而 LifecycleOwner 是 activity 或片段。

# A view model acceptor interface for views

public interface ViewModelAcceptor<T extends  ViewModel> {
    void plugViewModel(
        T viewModel,
        LifecycleOwner lifecycleOwner,
        FragmentManager fragmentManager
    );
}

# In the parent view class of the challenges new challenges are created
# in sequence

ChallengeFragment challengeFragment = new ChallengeFragment();
challengeFragment.setChallengeViewModel(challengeViewModel);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(this.getId(), challengeFragment);
fragmentTransaction.commit();

# ChallengeFragment

public class ChallengeFragment extends Fragment {

    private ChallengeViewModel challengeViewModel;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
        return new ChallengeView(getActivity(), null);
    }

    public void setChallengeViewModel(ChallengeViewModel challengeViewModel) {
        this.challengeViewModel = challengeViewModel;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ChallengeView challengeView = (ChallengeView) getView();
        Objects.requireNonNull(challengeView)
                .plugViewModel(challengeViewModel, this, getFragmentManager());
    }

}
# Challenge views are the child views of the sequence

public class ChallengeView extends ConstraintLayout implements ViewModelAcceptor<ChallengeViewModel> {
  [...]
}