接着上一篇文章:基于MVVM架构封装Flutter基础库
这篇文章主要使用基础库对MVVM做简单实践:Demo地址
一、MVVM回顾
Model:数据模型
1、XApi
2、通常来说,Model中保存了相关业务的数据,负责提取和处理数据,数据来源可以是本地数据库,也可以来自网络;
View:视图
1、BaseView
2、View只做和UI相关的工作,不涉及任何业务逻辑,不涉及操作数据,不处理数据;
3、通俗讲就是展示给用户的界面及控件,比如Flutter中参与界面展示的Widget;
ViewModel:视图模型
1、BaseViewModel
2、ViewModel将View和Model进行解耦,并且实现View与Model的交互;
3、简单讲就是所有的业务逻辑都由它负责,而不是将业务逻辑和View都糅合在一起;
Data Binding:绑定器
1、Provider
2、View通过数据绑定来关心ViewModel的数据变化;
3、通过Provider的Consumer/Selector等组件来实现数据绑定;
二、基础库MVVM组件介绍
1、BaseView功能说明
通过Consumer实现View和ViewModel绑定,ViewModel数据变化时通知View刷新;
BaseView配合BaseViewModel使用,进入页面时通过BaseViewModel.onLoading()方法触发http请求;
BaseView通过FutureBuilder实现异步UI更新,从而实现http通用加载错误页,空白页以及正常显示页UI和逻辑;
///@date: 2021/3/1 13:38
///@author: lixu
///@description:View 基类,配合[BaseViewModel]使用
///封装http加载错误页,空白页以及正常显示页UI和逻辑,UI可自定义
class BaseView<T extends BaseViewModel> extends StatefulWidget {
///加载成功后显示的页面
final Widget child;
///加载中页面
final Widget loadingChild;
///数据为空的页面
final Widget emptyChild;
///请求失败显示的页面
final Widget errorChild;
BaseView({@required this.child, this.loadingChild,
this.emptyChild, this.errorChild}) : assert(child != null);
@override
_BaseViewState<T> createState() => _BaseViewState<T>();
}
class _BaseViewState<T extends BaseViewModel> extends State<BaseView>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
///数据绑定
return Consumer<T>(
child: widget.child,
builder: (BuildContext context, T viewModel, Widget child) {
if (viewModel.isSuccess()) {
return child;
} else {
///异步UI更新
return FutureBuilder(
///触发网络请求:BaseViewModel.onLoading(context)
future: viewModel.onLoading(context),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
if (viewModel.isFail()) {
///加载失败
return _getErrorWidget(viewModel);
} else if (viewModel.isEmpty()) {
///数据为空
return _getEmptyWidget(viewModel);
} else {
///加载成功
return child;
}
} else {
///加载中
return _getLoadingWidget();
}
},
);
}
},
);
}
...省略部分代码
@override
bool get wantKeepAlive => true;
}
2、BaseCommonViewModel功能说明
ViewModel 顶层基类,进入View页面时,直接显示UI(不需要请求http获取数据)的 场景__继承BaseCommonViewModel类;
///@date: 2021/3/1 11:05
///@author: lixu
///@description: ViewModel 基类
///进入View页面时,直接显示UI(不需要请求http获取数据)的场景继承[BaseCommonViewModel]类
abstract class BaseCommonViewModel with ChangeNotifier {
///是否已经调用了dispose()方法
bool _isDispose = false;
///是否正在请求中
bool isLoading = false;
///网络请求对象,充当MVVM的Model层
XApi api = XApi();
///获取tag,用于日志tag
String getTag();
///保存请求token,用于页面关闭时取消请求
List<CancelToken> cancelTokenList = [];
///刷新页面
@override
notifyListeners() {
LogUtils.v(getTag(), 'notifyListeners() isDispose:$_isDispose');
if (!_isDispose) {
super.notifyListeners();
}
}
bool get isDispose => _isDispose;
///页面关闭时回调该方式,释放资源
///使用ChangeNotifierProvider的默认构造方法来注入ViewModel,才能保证资源能正确释放
@override
void dispose() {
super.dispose();
///页面关闭取消请求
api?.cancelList(cancelTokenList);
_isDispose = true;
LogUtils.v(getTag(), 'dispose()');
}
}
3、BaseViewModel功能说明
ViewModel 基类_,_进入页面时,需要http获取数据后才能显示UI的场景继承BaseViewModel类,配合 BaseView使用,实现http加载错误页,空白页以及正常显示页UI和逻辑;
///@date: 2021/3/1 11:09
///@author: lixu
///@description: ViewModel基类
///进入View页面时,需要http获取数据后才能显示UI的场景继承[BaseViewModel]类,配合[BaseView]使用
///泛型T:进入页面时,http获取数据对象的类型
///1.列表加载(分页加载)
///2.单个对象加载
abstract class BaseViewModel<T> extends BaseCommonViewModel {
...省略其它代码
///接口获取的是否是列表数据,否则就是单个数据对象
///默认true
bool _isRequestListData;
///数据源:针对列表请求
List<T> dataList;
///数据源:针对单个对象请求
T dataBean;
///获取http请求参数
Map<String, dynamic> getRequestParams();
///获取http请求url
String getUrl();
///加载数据:进入页面时触发该方法
Future onLoading(BuildContext context) async {
LogUtils.i(getTag(), 'onLoading');
if (isLoading) {
LogUtils.w(getTag(), 'onLoading() is Loading');
return;
}
isLoading = true;
_refreshedText = '刷新成功';
CancelToken cancelToken = CancelToken();
cancelTokenList.add(cancelToken);
if (_isRequestListData) {
///请求列表数据
await api.requestList<T>(
getUrl(),
params: getRequestParams(),
isShowLoading: false,
isShowFailToast: isShowFailToast(false),
cancelToken: cancelToken,
onSuccess: (List<T> list) {
///请求成功回调
dataList = [];
list = list ?? [];
///解析json
dataList.addAll(list);
if (_isPageLoad) {
///list分页加载
if (list.length < _pageSize) {
_canLoadMore = false;
} else {
_canLoadMore = true;
_pageNum++;
}
} else {
///list没有分页加载
_canLoadMore = false;
}
},
onError: (HttpErrorBean errorBean) {
///请求失败回调
_refreshedText = '刷新失败';
onErrorCallback(errorBean);
},
onComplete: () {
///请求完成回调
isLoading = false;
cancelTokenList?.remove(cancelToken);
},
);
} else {
///请求单个数据对象
await api.request<T>(
getUrl(),
params: getRequestParams(),
isShowLoading: false,
isShowFailToast: isShowFailToast(false),
cancelToken: cancelToken,
onSuccess: (T bean) {
///请求成功回调
dataBean = bean;
},
onError: (HttpErrorBean errorBean) {
///请求失败回调
_refreshedText = '刷新失败';
onErrorCallback(errorBean);
},
onComplete: () {
///请求完成回调
isLoading = false;
cancelTokenList?.remove(cancelToken);
},
);
}
}
///请求失败:重试刷新页面
void retryRefresh() {
if (!isDispose) {
notifyListeners();
} else {
LogUtils.e(getTag(), 'call retryRefresh() had dispose()');
}
}
///请求是否成功
bool isSuccess() {
if (_isRequestListData) {
return dataList != null && dataList.length > 0;
} else {
return dataBean != null;
}
}
///请求是否失败
bool isFail() {
if (_isRequestListData) {
return dataList == null;
} else {
return dataBean == null;
}
}
///请求数据是否为空
bool isEmpty() {
if (_isRequestListData) {
return dataList == null || dataList.isEmpty;
} else {
return dataBean == null;
}
}
...省略其它代码
}
三、MVVM实践
基于下面场景:
1. 打开用户列表页面,调用接口获取用户信息成功后显示用户列表UI;
2. 调用接口失败或数据为空,显示对应的占位页面,占位UI可以自定义;
3. 请求未完成时关闭页面,自动取消请求;
1、Model(UserDetailBean和XApi)
///@date: 2021/3/2 11:20
///@author: lixu
///@description: 用户详情对象
class UserDetailBean {
///头像
String icon;
///用户id
String userId;
///用户名
String name;
UserDetailBean.fromJsonMap(Map<String, dynamic> map)
: userId = map["userId"]?.toString(),
name = map["name"],
icon = map["icon"];
}
2、View(UserPage)
///@date: 2021/3/11
///@author: lixu
///@description:用户列表页面
///进入页面时调用接口(用户列表)获取数据成功后,才能显示UI
class UserPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('用户列表页面(MVVM)')),
///使用ChangeNotifierProvider的默认构造方法来注入ViewModel,
///保证资源能正确释放
body: ChangeNotifierProvider(
create: (_) {
return UserListViewModel();
},
child: BaseView<UserListViewModel>(
///UserListView为请求成功后显示的View(用户列表)
child: UserListView(),
///自定义请求失败的View
///如果不设置,会使用全局失败页面(IResConfig中配置的资源)
errorChild: Center(
child: Text(
'这是自定义请求失败的页面:\n请求失败,点击重试',
style: TextStyle(color: Colors.red,
fontSize: 20),
textAlign: TextAlign.center,
),
),
),
),
);
}
}
3、ViewModel(UserListViewModel)
///@date: 2021/3/2 11:26
///@author: lixu
///@description: 用户列表viewModel
///泛型[UserDetailBean]:进入页面时,http获取数据对象的类型
class UserListViewModel extends BaseViewModel<UserDetailBean> {
UserListViewModel() : super(isPageLoad: false);
///获取http请求参数
@override
Map<String, dynamic> getRequestParams() {
return {
'userId': loginInfo.userBean?.userId,
'token': loginInfo.token,
};
}
@override
String getTag() {
return 'UserListViewModel';
}
///获取http请求url
@override
String getUrl() {
return HttpUrls.userListUrl;
}
}
4、思考下面场景
上面mvvm 实践,进入页面时需要调用一个接口获取数据成功后才显示UI,如果需要调用多个接口成功后才能显示UI,该如何处理?
建议对每个接口封装独立的ViewModel来处理逻辑,参考代码如下:
///@date: 2021/3/11
///@author: lixu
///@description:用户列表页面2
///进入页面时调用2(多个)个接口获取数据成功后,才能显示UI
///1、获取token接口[TokenViewModel]
///2、用户列表接口[UserListViewModel]
class UserPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('用户列表页面(MVVM)')),
body: MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) {
return TokenViewModel();
}),
ChangeNotifierProvider(create: (_) {
return UserListViewModel();
}),
],
///1、TokenViewModel 获取token接口
child: BaseView<TokenViewModel>(
///2、UserListViewModel 获取用户信息的接口
child: BaseView<UserListViewModel>(
///请求成功显示的UI
child: UserListView(),
///数据为空的UI
emptyChild: Center(
child: Text(
'这是自定义请求数据为空的页面:\n数据为空,点击刷新',
style: TextStyle(color: Colors.blue, fontSize: 20),
textAlign: TextAlign.center,
),
),
),
),
),
);
}
}
如果第一个接口的响应要作为第二个接口的请求参数,该如何处理?参考这里
四、总结
通过使用:BaseView+BaseViewModel+Provider+XApi 对MVVM进行了简单的实现,更多功能可以下载demo体验
上一篇文章:Flutter Dio封装实践
今天的文章Flutter MVVM 简单实践分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/16513.html