Provider 原理介绍与实战(MVVM)(下)
MVVM 是什么和优势
MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。当然这些事 ViewModel 已经帮我们做了,它可以取出 Model 的数据同时帮忙处理 View 中由于需要展示内容而涉及的业务逻辑
优点
MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),
1. 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
2. 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
3. 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,使用Expression Blend可以很容易设计界面并生成xaml代码。
4. 可测试。界面素来是比较难于测试的,测试可以针对ViewModel来写。
View绑定到ViewModel,然后执行一些命令在向它请求一个动作。而反过来,ViewModel跟Model通讯,告诉它更新来响应UI。这样便使得为应用构建UI非常的容易。
Flutter中的MVVM模式的几种方式
在不使用任何第三方包的时候,官方也提供了不错的选择,那就是StatefulWidget
,当我们需要改变状态来刷新UI时,只需要调用setState()
方法。
这种方法简单直接,而且也可以理解为一种MVVM模式,只不过View和Model仍然耦合在一起,ViewModel并没有承担起它应有的角色。随着我们的工程变得越来越大时,代码里的setState()
就会变得越来越多,显得非常混乱,并且有时候会忘记调用setState()
,导致浪费很多时间来定位问题。
官方早期也提供的一种状态管理模式叫做BLOC
。这种方式依赖于第三方包rxDart
,以流(Stream)的方式很好地解决了setState()
的问题。但是这种学习难度较大,对Flutter的新手并不友好。后来出现了一种第三方库Provider
,这是一种先进的状态管理和依赖注入的工具,并且易于学习和理解,所以目前官方也推荐首选Provider
。
使用ViewModel进行状态管理
既然我们在MVVM模式模式下进行开发APP,那么ViewModel是必不可少的。也就是当状态属性变化时,我们需要UI(也就是View层)进行相应的更改。
Provider中有ChangeNotifierProvider
可以帮助我们监听是否状态发生了变化,它的child参数是一个Consumer
可以帮我们来消费状态的变化。通俗来讲就是在这里调用Widget的build方法来进行UI刷新。
那在哪里去触发状态变化的通知呢?答案就是使用ChangeNotifier
,当调用其中的notifyListeners()
方法时,就可以通知监听它的ChangeNotifierProvider
进行刷新。
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<T>(
child: Consumer<T>(
//Widget的builder方法与child
builder: widget.builder,
child: widget.child,
),
create: (BuildContext context) {
//这里是我们的ViewModel,一个ChangeNotifier
return model;
},
);
}
现在我们的生产者消费者都有了,可以完善我们的MVVM模式了。
基础页面逻辑分析
一般页面载入的时候会显示一个loading,然后加载成功展示数据,失败就展示失败页面

所以枚举一个页面状态:
enum ViewState { Idle, Busy,Error }
ViewModel都会在页面状态属性改变后更新ui,通常会调用notifyListeners,把这一步移到BaseModel中:
class BaseModel extends ChangeNotifier {
ViewState _state = ViewState.Loading;
ViewState get state => _state;
void setState(ViewState viewState) {
_state = viewState;
notifyListeners();
}
}
ui里需要ChangeNotifierProvider提供Model,并且用Consumer更新ui。因此我们也将其内置到BaseView中:
enum ViewState { Loading, Success, Failure, None }
class BaseModel extends ChangeNotifier {
ViewState _state = ViewState.None;
ViewState get state => _state;
void setState(ViewState viewState) {
_state = viewState;
notifyListeners();
}
}
class BaseWidget<T extends ChangeNotifier> extends StatefulWidget {
final Widget Function(BuildContext context, T model, Widget child) builder;
final T model;
final Widget child;
final Function(T) onModelReady;
BaseWidget({
Key key,
this.builder,
this.model,
this.child,
this.onModelReady,
}) : super(key: key);
_BaseWidgetState<T> createState() => _BaseWidgetState<T>();
}
class _BaseWidgetState<T extends ChangeNotifier> extends State<BaseWidget<T>> {
T model;
@override
void initState() {
model = widget.model;
if (widget.onModelReady != null) {
widget.onModelReady(model);
}
super.initState();
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<T>(
create: (BuildContext context) => model,
child: Consumer<T>(
builder: widget.builder,
child: widget.child,
),
);
}
}
我们用封装的基类完成登录页面:
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BaseWidget<LoginViewModel>(
model: LoginViewModel(loginServive: LoginServive()),
builder: (context, model, child) => Scaffold(
appBar: AppBar(
title: Text('provider'),
),
body: Column(
children: <Widget>[
model.state == ViewState.Loading
? Center(
child: CircularProgressIndicator(),
)
: Text(model.info),
FlatButton(
color: Colors.tealAccent,
onPressed: () => model.login("pwd"),
child: Text("登录")),
],
),
),
);
}
}
/// viewModel
class LoginViewModel extends BaseModel {
LoginServive _loginServive;
String info = '请登录';
LoginViewModel({@required LoginServive loginServive})
: _loginServive = loginServive;
Future<String> login(String pwd) async {
setState(ViewState.Loading);
info = await _loginServive.login(pwd);
setState(ViewState.Success);
}
}
/// api
class LoginServive {
static const String Login_path = 'xxxxxx';
Future<String> login(String pwd) async {
return new Future.delayed(const Duration(seconds: 1), () => "登录成功");
}
}
参考
转载自:https://juejin.cn/post/6975904656245391373