MVVM
MVVM in Flutter using Dart Streams
本文将展示MVVM在Flutter中的代码实现。我们将使用Dart的Stream API创建函数响应式的ViewModel。
MVVM(Model-View-ViewModel)
MVVM的主要目标就是尽可能将View里的状态及逻辑分离出来,移到ViewModel实体里。ViewModel同样包含业务逻辑,这样ViewModel就相当于一个介于View和Model的中间层。
ViewModel有两个基本的只能:
- 响应用户的输入(比如改变模型,初始化网络请求或者路由到其他页面)。
- 提供数据的输出,这些数据View可以订阅。
View不包含任何业务逻辑。View的只能如下:
- 响应ViewModel新的输出状态,并且渲染出与之相应的界面(比如显示一个字符串或者文本框等)。
- 通知ViewModel用户新的输入(比如按钮点击,文本改变,屏幕触摸等)。
##Flutter中的MVVM
Flutter中,Widget表示MVVM模式中的View。逻辑代码包含在ViewModeel类中。ViewModel是完全平台无关的,对Flutter没有依赖,因此,在一个Web项目中服用也比较容易。
Example:邮件订阅Widget
例子:实现一个简单注册表单,由一个输入框和一个提交按钮组成。邮件地址非法的话,按钮不可操作,用户也可以看到一个错误提示。
视图逻辑,视图状态,业务逻辑全部混在一起。将导致一下问题:
1.难以进行单元测试。
2.难以复用,其它的Dart项目不能复用业务逻辑,因为它混入了Flutter相关的视图逻辑。
3.代码庞大混乱,当Widget变得巨大时会比较凌乱。
使用MVVM解决
前面解释过,ViewModel有输入输出。所以我们将添加输入输出。
所有的输入都是Skins。View可以通过它向ViewModel添加数据。
所有的输出都是Streams。View可以通过订单这些流监听改变。
ViewModel的接口如下:
abstract class SubscriptonViewModel {
Sink get inputMailText;
Stream<bool> get outputIsButtionEnabled;
Stream<String> get outputErrorText;
void dispose();
}
可以使用StreamController作为输入。可以在内部使用StreamController提供的流处理这些输入事件。
class SubscriptionViewModelImpl implements SubscriptionViewModel {
var _mailTextController = StreamController<String>.broadcast();
@override
Skin get inputMailText => _mailTextController;
@override
Stream<bool> get outputIsButtonEnable => _mailTextController.stream.map((email) => EmailValidator.isEmailValid(email));
@override
Stream<String> get outputErrorText => outputIsButtonEnabled.map((isEnabled) => isEnabled ? null : "Invalid email");
@override
void dispose() => _mailTextController.close();
}
绑定View到ViewModel
怎样提供输入及处理输出事件?
通过给ViewModel的Skins添加一个输入值,这样就给ViewModel提供了输入。将Widget绑定到ViewModel。这个例子中,当文本框文本改变后,我们添加该文本到ViewModel的Sinks。
@override
void initState() {
controller.addListener(() => _viewModel.inputMailText.add(controller.text));
super.initState();
}
需要通过订阅Output-Streams监听ViewModel的输出。
Flutter提供了一种Widget叫StreamBuilder,当一个Stream提供了新值时它可以自动更新。不再需要调用"setState"。
StreamBuilder的"build"方法给我们提供了快照。快照包含于流的信息,数据及错误。如果流没有发射任何值,数据将是null。
StreamBuilder<String>(
stream: _viewModel.outputErrorText,
build: (context, snapshot) {
return MyTextField(
controller: controller,
hintText: "Email",
errorText: snapshot.data);
}
)
在使用Stream时为了帮助Dart编译器,避免你运行时错误,要为Stream添加所有需要的类型。
全部代码:
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: StreamBuilder<String>(
stream: _viewModel.outputErrorText,
builder: (context, snapshot) {
return MyTextField(
controller: controller,
hintText: "Email",
errorText: snapshot.data);
}
)
),
StreamBuilder(
stream: _viewModel.outputIsButtonEnabled,
builder: (context, snapshot) {
return SubmitButton(enabled: snapshot.data ?? false);
}
)
]
);
}
在复杂任务中可以使用RxDart。
上一篇: Checkbox 多选框
下一篇: 点击结账.