MVVM。如何将复杂数据/命令从Domain传输到View

问题描述 投票:1回答:3

简而言之,问题是:在MVVM(AAC)中,域(业务逻辑)如何管理View层中复杂状态/数据的显示?

现在更详细。

这意味着在域内:1)收到,计算出一些需要显示的数据; 2)状态已经改变,有必要对此作出反应(隐藏/显示一组小部件,调用新的片段,显示/更新进度等)。除了显示消息或对话框,或者只是将LiveData发送到RecyclerView之外,它更难做到。

因此,像“hello world”或“2 + 2 = 4”这样的例子并不合适,一切都很清楚。在MVP中,这很简单。但在这里我能够找到MVVM的弱点。

现在我做了以下事情。

通过RxJava2(作为选项,它可以是来自AAC的LiveData)从Domain到View(通过ViewModel AAC)传递包含命令类型(enum)的对象,并且具有用于所有场合的数据的一堆字段(当然,不同命令的不同字段)。此外,View包含一个大的switch-case,具体取决于处理所有这些的命令类型。

变种2.要创建一堆特定的对象,然后在视图中将坐一个大的if-instanceof

变体3.在ViewModel AAC中存储View的数据(实际上是它的目的),并仅从Domain发送命令类型,然后View从ViewModel获取所有必要的数据。

变体4.堆(在复杂的UseCases的情况下)在域中的特定Observables和视图中的subscribers堆。

那么:有没有(如果有的话)更优雅的方式?可能存在一些架构模式。也许我是徒劳的反应,这是正确的方法。

PS。 1)这里的“命令”模式并不完全适合,2)“状态”模式已经由我实现,并且它也没有解决问题。

android design-patterns mvvm rx-java android-architecture-components
3个回答
1
投票

在MVP中,这很简单。但在这里我能够找到MVVM的弱点。

这不是MVVM的弱点,它只是MVP和MVVM实现的区别。

  • 在MVP中,您创建了一组接口,让View和Presenter相互通信;
  • 在MVVM中,您可以创建一个中介(例如LiveData)来桥接View和ViewModel。

恕我直言,你可以:

  • 在您的用例中,创建一个MediatorLiveData A来存储结果。
  • 在你的ViewModel中,创建一个MediatorLiveData B来观察A(即MediatorLiveData.addSource(A)
  • 在您的视图中,观察B以反映任何UI更新。

你可以在iosched18找到一个具体的例子。


1
投票

模型视图ViewModel体系结构

MVVM

  • 视图是用户界面,即布局。在Android中,这通常意味着Activity,Fragment或ViewHolder及其相应的膨胀XML布局文件。
  • 该模型是我们的业务逻辑层,它提供了与数据交互的方法。
  • 视图模型充当视图和模型之间的中间人,通过属性公开模型中的数据并包含UI状态。此外,它还定义了可以在点击等事件上调用的命令。视图模型包含应用程序的表示逻辑。

在MVVM架构模式中,视图和视图模型主要通过数据绑定相互交互。理想情况下,视图和视图模型不应该彼此了解。绑定应该是视图和视图模型之间的粘合剂,并处理两个方向上的大部分内容。然而,在Android中,它们不能真正独立:

  1. 您必须保存并恢复状态,但状态现在位于视图模型中。
  2. 您需要告诉您关于生命周期事件的视图模型。
  3. 您可能会遇到需要直接调用视图方法的情况。

对于这些情况,视图和视图模型都应该实现接口,然后在必要时通过命令用于通信。然而,在几乎所有情况下,仅需要视图模型的接口,因为数据绑定库处理与视图的交互,并且可以使用例如自定义组件。何时需要上下文。

视图模型还更新模型,例如通过向数据库添加新元素或更新现有元素。它还用于从模型中获取数据。理想情况下,模型还应该通知视图模型更改,但这取决于实现。

现在,一般来说,视图和视图模型的分离使得表示逻辑易于测试,并且从长远来看也有助于维护。与数据绑定库一起,这意味着更少的代码和更清晰的代码。

例:

  <layout xmlns:android="...">
  <data>
    <variable name="vm" type="pkg.MyViewModel" />
  </data>

  <FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:visibility="@{vm.shouldShowText}"
      android:text="@={vm.text}" />

    <Button
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:onClick="@{vm::onButtonClick}"
      android:text="@string/button"/>
  </FrameLayout>
</layout>

如果要使用MVVM体系结构,则布局应仅引用一个变量,即此视图的特定视图模型,在本例中为MyViewModel。在视图模型中,您提供布局的属性。这可以像从模型对象返回String一样简单,也可以更复杂,具体取决于您的用例。

public class MyViewModel extends BaseObservable {
   private Model model = new Model();

   public void setModel(Model model) {
       this.model = model;
       notifyChange();
   }

   public boolean shouldShowText() {
       return model.isTextRequired();
   }

   public void setText(String text) {
       model.setText(text);
   }

   public String getText() {
       return model.getText();
   }

   public void onButtonClick(View v) {
       // Save data
   }
}

这里我们有一个文本属性。由于我们有一个用于用户输入的EditText,我们可以使用双向数据绑定,也可以让数据绑定库将输入保存回视图模型。为此,我们创建了一个setter和一个getter,并将该属性绑定到EditText的text属性,但这次在括号前面有一个=符号,它向库发出了我们想要双向数据绑定的信号。

此外,我们只想在模型显示需要文本输入时显示EditText。为此,我们在视图模型中提供了一个布尔属性,并将其绑定到visibility属性。为了实现这一点,我们还必须创建一个绑定适配器,它在false时将可见性设置为GONE,在true时将可见性设置为VISIBLE。

@BindingAdapter("android:visibility")
public static void setVisibility(View view, boolean visible) {
  view.setVisibility(visible ? View.VISIBLE : View.GONE);
}

最后,我们希望在按下按钮时存储信息。为此,我们在视图模型中创建一个命令onButtonClick(),用于处理与模型的交互。在布局中,我们通过方法引用将命令绑定到Button的onClick属性。为了使其直接工作,我们的方法需要具有View类型的单个参数,就像OnClickListener一样。作为替代方案 - 如果您不想要View参数 - 您也可以直接在布局中使用lambda表达式。如您所见,使用视图模型进行数据绑定非常简单明了。

现在,重要的是要记住我们希望将表示逻辑放在我们的视图模型中以实现可测试性。避免将逻辑直接放在绑定中,即使数据绑定库允许它。不要忘记您也可以使用自定义绑定适配器,这通常可以简化操作。


0
投票

我意识到我需要向MVI模式的方向迁移。

并且有必要实现“单向数据流”。

很好,这个解决方案在这里描述Reactive Apps With Model-View-Intent - Part2 - View And Intent by Hannes Dorfman

另一个有趣的解决方案是库RxPM -- Reactive implementation of Presentation Model pattern in Android

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.