在上篇内容的基础上,我们已经可以写一些简单的应用了,但是可以预见到的是,随着页面功能的增多,函数个数、数据与页面的交互也会随之增对,随之而来的就是散落在各处的函数和setState,代码就越来越难维护了。因此需要适当的引入一些设计模式,将界面、数据与逻辑解耦。这里首先提一下MVP。
网络上文章很多。。。。。就不重复了。
这里直接拿代码说话,先说说MVP的具体实践,然后举个例子:同一个功能页面切换不同的数据源。
Dart语言貌似是没有interface的,这里用的是abstract class。
abstract class IView<T> {
setPresenter(T presenter);
}
abstract class IPresenter {
init();
}
假设我们有一个页面在加载的时候会从web接口获取数据并显示,同时页面可以响应用户的输入,比如上拉加载更多。则从页面行为层面我们可以抽象如下接口:
abstract class DemoPagePresenter implements IPresenter {
// 初始化时候加载第一批数据
void loadData();
// 用户操作加载更多
void loadMore();
}
abstract class DemoPageView implements IView<DemoPagePresenter> {
// 第一批数据加载成功
void onLoadSuccess(data);
// 第一批数据加载失败
void onLoadError();
// 更多数据加载成功
void onLoadMoreSuccess(data);
// 更多数据加载失败
void onLoadMoreError();
}
从这里可以看出, Presenter 主要负责事件驱动或者用户触发后的业务行为,在这里包括了页面初始化时候的加载数据loadData
和用户上拉列表时候的 loadMore
。
而View主要负责页面逻辑处理后的结果展现,比如加载成功、失败分别显示什么。
在Presenter和View都声明完毕后,即可以开始落地具体的实现代码了。这里我们用假代码描述。
1)Presenter的具体实现
Presenter 仅关心事件驱动和用户触发后的逻辑,并决定分别另View采用何种行动,本身并不关心View长什么样。此时的View对于Presenter而言,仅仅是几个抽象的接口函数。
class DemoPagePresenterImpl implementes DemoPagePresenter {
// 这里Presenter需要引入View的实例,来出发View的刷新
DemoPageView _view;
// View的实例也需要引入Presenter的实例引用
DemoPagePresenterImpl(this._view) {
_view.setPresenter(this);
}
// 这里泛指数据的查询服务,比如可以是Dio,或者Sqlite的查询封装
DataRepository _repository;
int currentPage = 0;
@override
init() {
// 初始化数据查询服务
_repository = DataRepository();
}
// 实现具体的业务逻辑,并决定采用何种View的操作,不关心View的函数背后的具体实现
@override
loadData() {
_repository.loadByPage(currentPage).then((data) {
_view.onLoadSuccess(data);
}).catchError((error){
_view.onLoadError();
});
}
// 实现具体的业务逻辑,并决定采用何种View的操作,不关心View的函数背后的具体实现
@override
loadMore() {
_repository.loadByPage(++currentPage).then((data) {
_view.onLoadMoreSuccess(data);
}).catchError((error){
_view.onLoadMoreError();
});
}
}
2)View的具体实现
View在Flutter中一般就和Widget绑定在一起了,让Widget implements 相应的View接口即可。注意在这里需要对View和Presenter的绑定操作做一定的处理,同时完成Presenter的init初始化。
请注意: 这里的State是View, State中的Presenter变量在State构造函数中就通过setPresenter指定好了。
这样View仅关心何时出发Presenter的业务处理代码,但是具体业务代码是如何实现的View并不关心。
class DemoPage extends StatefulWidget {
@override
_DemoPageState createState() {
_DemoPageState view = new _DemoPageState();
// 在这里初始化Presenter,同时与View建立绑定关系
DemoPagePresenter presenter = new DemoPagePresenterImpl(view);
presenter.init();
return view;
}
}
class _DemoPageState extends State<DemoPage> implementes DemoPageView {
DemoPagePresenter _presenter;
List _resultList = List();
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
RaisedButton(
onPressed: () { _presenter.loadMore(); },
child: Text('Press to load more')
),
ListView.builder(
itemCount: _resultList.length,
builder: (context, index) {
return Text(_resultList[index]);
}
),
]
);
}
@overrid
setPresenter(DemoPagePresenter presenter) {
_presenter = presenter;
}
@overrid
initState() {
super.initState();
// 初始化加载第一批数据
_presenter.loadData();
}
@override
onLoadSuccess(data) {
setState((){
_resultList = data;
});
}
@override
onLoadError(){
print('error!');
}
@override
onLoadMoreSuccess(data) {
setState((){
_resultList.addAll(data);
});
}
@override
onLoadMoreError(){
print('load more error!');
}
}
改需求最直接的反映就是开发环境和生产环境的切换了,在上边的例子来说就是根据需求切换DataRepository的不同实现类即可。这里截取部分片段
// 这里泛指数据的查询服务,比如可以是Dio,或者Sqlite的查询封装
DataRepository _repository;
int currentPage = 0;
@override
init() {
// 初始化数据查询服务
// _repository = DataRepository();
// 修改为如下:
if(appConstants.production) {
_repository = DataRepositoryProductionImpl();
} else {
_repository = DataRepositoryDevelopmentImpl();
}
}