by QuickBird Studios
通过QuickBird Studios
使用Model-View-ViewModel使用Dart Streams使Flutter中的代码更清晰 (Use Model-View-ViewModel to make your code cleaner in Flutter with Dart Streams)
A common problem when developing apps is that you end up with over-complicated classes. These classes contain view logic as well as business logic. Both are so intertwined that it’s impossible to test them independently. Code-readability suffers, and future code changes are hard to implement.
开发应用程序时的一个常见问题是您最终遇到了过于复杂的类。 这些类包含视图逻辑和业务逻辑。 两者是如此交织在一起,以至于无法独立测试它们。 代码可读性受到影响,将来的代码更改很难实现。
Since there are almost no constraints on your architecture in Flutter, it’s fairly easy to run into this problem. Some developers write all their code in the Widget until they realize the mess they produced. Reusing code in other projects seems impossible, and in the end, you write most of your code twice. Model-View-ViewModel (MVVM) tries to solve that by splitting up business logic and view details.
由于在Flutter中,您的体系结构几乎没有任何限制,因此遇到此问题相当容易。 一些开发人员在Widget中编写所有代码,直到他们意识到产生的混乱。 在其他项目中重用代码似乎是不可能的,最后,您将大部分代码编写了两次。 Model-View-ViewModel(MVVM)试图通过拆分业务逻辑和查看详细信息来解决此问题。
In this article, we show you how MVVM with Flutter could look like. We’ll create a functional reactive ViewModel using Dart’s Stream API.
在本文中,我们向您展示带有Flutter的MVVM的外观。 我们将使用Dart的Stream API创建功能性的React式ViewModel。
模型-视图-视图模型 (Model-View-ViewModel)
Before we look at any code, we should get a basic understanding of MVVM. If you’re familiar with MVVM, you can skip this part.
在看任何代码之前,我们应该对MVVM有基本的了解。 如果您熟悉MVVM,则可以跳过此部分。
The main goal behind MVVM is to move as much of the state and logic from the View into a separate entity. This name given to this entity is the ViewModel. The ViewModel also contains the business logic. It serves as the mediator between the View and the Model.
MVVM的主要目标是将视图中尽可能多的状态和逻辑转移到一个单独的实体中。 赋予该实体的名称是ViewModel。 ViewModel还包含业务逻辑。 它充当视图和模型之间的中介。
The ViewModel has basically two responsibilities:
ViewModel基本上有两个职责:
- it reacts to user inputs (e.g. by changing the model, initiating network requests, or routing to different screens) 它对用户输入做出React(例如,通过更改模型,发起网络请求或路由到不同的屏幕)
- it offers output data that the View can subscribe to 它提供View可以订阅的输出数据
The View does not contain any business logic. These are the responsibilities of the view:
视图不包含任何业务逻辑。 这些是视图的职责:
- it reacts to new output states of the ViewModel and renders them accordingly (e.g. by showing a String in a text field) 它对ViewModel的新输出状态做出React并相应地渲染它们(例如,通过在文本字段中显示String)
- it tells the ViewModel about new user inputs (e.g. button-clicks, text-changes, screen touches) 它告知ViewModel新的用户输入(例如,按钮单击,文本更改,屏幕触摸)
In contrast to popular MVC approaches, the Fragment / Activity / UIViewController or Widget does not contain business logic in MVVM. It is a humble view that renders the ViewModel’s output states. The ViewModel does not know the View (a difference from forms of MVP and MVC). It offers output states that the View observes:
与流行的MVC方法相反,Fragment / Activity / UIViewController或Widget在MVVM中不包含业务逻辑。 这是一个简陋的视图,呈现ViewModel的输出状态。 ViewModel不知道视图(与MVP和MVC的形式有所不同)。 它提供视图观察到的输出状态:
Flutter中的MVVM (MVVM in Flutter)
In Flutter, the Widget represents the View of MVVM. The business logic sits in a separate ViewModel-class. The ViewModel is totally platform-independent. It contains no dependencies to Flutter, and can be easily reused, for example in a web project.
在Flutter中,小部件表示MVVM的视图。 业务逻辑位于单独的ViewModel类中。 ViewModel完全独立于平台。 它不包含对Flutter的依赖关系,并且可以轻松地重用,例如在Web项目中。
That is one of MVVM’s biggest powers. We can create a Mobile App and a website that both share the same ViewModel. You don’t need to reinvent and write the logic twice.
这是MVVM的最大功能之一。 我们可以创建共享相同ViewModel的移动应用程序和网站。 您无需重新发明并编写逻辑两次。
示例:电子邮件订阅小部件 (Example: Email Subscription Widget)
Let’s look at an example. We’ll implement a Newsletter signup-form with an email textfield and a submit button. The button is disabled and the user sees an error if the email is invalid:
让我们来看一个例子。 我们将实现带有电子邮件文本字段和提交按钮的新闻简报注册表单。 如果电子邮件无效,该按钮将被禁用,并且用户会看到错误消息:
丑陋的方式 (The ugly way)
Without any specific architecture, the business logic and the current state are part of the widget. It could look something like this:
没有任何特定的体系结构,业务逻辑和当前状态就是小部件的一部分。 它可能看起来像这样:
The problem is that view logic, view state, and business logic are mixed up. That leads to a few problems:
问题是视图逻辑,视图状态和业务逻辑混合在一起。 这会导致一些问题:
- It’s hard to unit test 很难进行单元测试
- Other Dart projects cannot reuse the business logic, since it’s intertwined with Flutter-dependent View logic 其他Dart项目无法重用业务逻辑,因为它与依赖Flutter的View逻辑交织在一起
- This style gets messy very soon and you end up with huge Widget classes 这种样式很快就会变得凌乱,您最终会得到大量的Widget类
Let’s see how we can improve this…
让我们看看如何改善这一点……
MVVM解决方案 (Solution with MVVM)
As explained above, the ViewModel has Input and Output parameters. We will add an ‘input’ or ‘output’ prefix for the sake of clarity.
如上所述,ViewModel具有输入和输出参数。 为了清楚起见,我们将添加“ input ”或“ output ”前缀。
All Inputs are Sinks
. The View can use those to insert data into the ViewModel. All Outputs are Streams
. The View can listen for changes by subscribing to the Streams
. The interface for our ViewModel looks like this:
所有输入均为Sinks
。 视图可以使用这些视图将数据插入到视图模型中。 所有输出均为Streams
。 View可以通过订阅Streams
来侦听更改。 我们的ViewModel的界面如下所示:
We are using a StreamController
as an input Sink
. This StreamController
provides a stream that we can use internally to handle those input events.
我们使用StreamController
作为输入Sink
。 这个StreamController
提供了一个流,我们可以在内部使用它来处理那些输入事件。
将视图绑定到ViewModel (Binding a View to the ViewModel)
So how do we supply inputs and handle output events? To supply input values to the ViewModel, we insert them into the ViewModel’s Sinks
. We’ll bind a Widget to the ViewModel. In this case, we insert the TextField’s text whenever it changes.
那么,我们如何提供输入并处理输出事件呢? 为了向ViewModel提供输入值,我们将它们插入到ViewModel的Sinks
。 我们将小部件绑定到ViewModel。 在这种情况下,只要文本字段发生更改,我们就将其插入。
You listen to the ViewModel Outputs by subscribing to the Output-Streams.
您可以通过订阅的输出- 流倾听视图模型输出。
Flutter provides a really cool Widget called StreamBuilder
that will update whenever a Stream provides a new value. We won’t call setState
ever again!The StreamBuilder’s builder
method gives you a snapshot whenever it builds. This snapshot contains information about the stream, its data, and its errors. If our stream did not emit any value, snapshot.data
will be null. So, be careful.
Flutter提供了一个非常酷的小部件,称为StreamBuilder
,它将在Stream时更新 提供新的价值。 我们不会再调用setState
! StreamBuilder的 builder
方法会在builder
为您提供快照。 该快照包含有关流,其数据及其错误的信息。 如果我们的流没有发出任何值,则snapshot.data
将为null。 所以要小心
QUICK TIP: Try to help the Dart compiler when working with streams. Add all the needed generic types to avoid runtime errors.
快速提示:在使用流时,请尝试帮助Dart编译器。 添加所有必需的通用类型,以避免运行时错误。
Here you can see the whole picture:
在这里您可以看到整个图片:
As you can see, the View’s only responsibility is rendering Outputs and supplying Inputs to the ViewModel. Our Widget is therefore super slim and easy-to-read.
如您所见,View的唯一职责是呈现输出并将输入提供给ViewModel。 因此,我们的小部件超薄且易于阅读。
结论 (Conclusion)
We started out with MVVM in the native world and wondered if it would also work with Flutter. After trying it out, we can say: MVVM is a great fit for Flutter as well.
我们从原生的MVVM开始,想知道它是否也可以与Flutter一起使用。 在尝试之后,我们可以说:MVVM也非常适合Flutter。
We love how nicely the View-logic is separated from the business logic. We love how easy ViewModels can be unit-tested. And we love how Dart ViewModels can be shared with other platforms that are using Dart.
我们喜欢将视图逻辑与业务逻辑很好地分离。 我们喜欢ViewModels可以轻松进行单元测试。 我们喜欢Dart ViewModels如何与使用Dart的其他平台共享。
The Stream-API takes some time to get used to, but afterward it feels very smooth. For more complicated tasks we used RxDart. This adds functionality to the standard Stream-API.
Stream-API需要一些时间来习惯,但是之后感觉很流畅。 对于更复杂的任务,我们使用了RxDart。 这将功能添加到标准Stream-API。
If you’re just hacking a small app, then the normal “put-everything-in-one-class” approach might be more straightforward. If you plan to build a bigger app, though, MVVM might be the architecture for you.
如果您只是在破解一个小型应用程序,那么通常的“一劳永逸”的方法可能会更简单。 但是,如果您打算构建更大的应用程序,那么MVVM可能是适合您的体系结构。
Originally published at quickbirdstudios.com on June 12, 2018.
最初于2018年6月12日发布在quickbirdstudios.com上。
翻译自: https://www.freecodecamp.org/news/app-architecture-mvvm-in-flutter-using-dart-streams-26f6bd6ae4b6/