前言
当前案例 Flutter SDK版本:3.22.2
每当我们开始一个新项目,都会 引入常用库、封装工具类,配置环境等等,我参考了一些文档,将这些内容整合、简单修改、二次封装,得到了一个开箱即用的Flutter开发模版,即使看不懂封装的工具对象原理,也没关系,模版化的使用方式,小白也可以快速开发Flutter项目。
快速上手
用到的依赖库
dio: ^5.4.3+1 // 网络请求
fluro: ^2.0.5 // 路由
pull_to_refresh: ^2.0.0 // 下拉刷新 / 上拉加载更多
sentry_flutter: ^7.8.0 // 异常上报
修改规则
默认使用的是Flutter团队制定的规则,但每个开发团队规则都不一样,违反规则的地方会出现黄色波浪下划线,比如我定义常量喜欢字母全部大写,这和默认规则不符;
修改 Flutter项目里的 analysis_options.yaml 文件,找到 rules,添加以下配置;
rules:
use_key_in_widget_constructors: false
prefer_const_constructors: false
package_names: null
修改前
修改后
MVVM
- MVVM 设计模式,相信大家应该不陌生,这里并不是传统的MVVM,我魔改了,简单说一下每层主要负责做什么;
- Model: 数据相关操作;
- View:UI相关操作;
- ViewModel:业务逻辑相关操作。
持有关系:
View持有 ViewModel;
Model持有ViewModel;
ViewModel持有View;
ViewModel持有Model;
注意:这种持有关系,有很高的内存泄漏风险,所以我在基类的 dispose() 中进行了销毁,子类重写一定要调用 super.dispose();
/// BaseStatefulPageState的子类,重写 dispose()
/// 一定要执行父类 dispose(),防止内存泄漏
@override
void dispose() {
/// 销毁顺序
/// 1、Model 销毁其持有的 ViewModel
if(viewModel?.pageDataModel?.data is BaseModel?) {
BaseModel? baseModel = viewModel?.pageDataModel?.data as BaseModel?;
baseModel?.onDispose();
}
if(viewModel?.pageDataModel?.data is BasePagingModel?) {
BasePagingModel? basePagingModel = viewModel?.pageDataModel?.data as BasePagingModel?;
basePagingModel?.onDispose();
}
/// 2、ViewModel 销毁其持有的 View
/// 3、ViewModel 销毁其持有的 Model
viewModel?.onDispose();
/// 4、View 销毁其持有的 ViewModel
viewModel = null;
/// 5、销毁监听App生命周期方法
lifecycleListener?.dispose();
super.dispose();
}
基类放在文章最后说,这里先忽略;
Model
class HomeListModel extends BaseModel {
... ...
ValueNotifier<int> tapNum = ValueNotifier<int>(0); // 点击次数
@override
void onDispose() {
tapNum.dispose();
super.onDispose();
}
... ...
}
... ...
View
class HomeView extends BaseStatefulPage<HomeViewModel> {
HomeView({super.key});
@override
HomeViewState createState() => HomeViewState();
}
class HomeViewState extends BaseStatefulPageState<HomeView, HomeViewModel> {
@override
HomeViewModel viewBindingViewModel() {
/// ViewModel 和 View 相互持有
return HomeViewModel()..viewState = this;
}
/// 初始化 页面 属性
@override
void initAttribute() {
... ...
}
/// 初始化 页面 相关对象绑定
@override
void initObserver() {
... ...
}
@override
void dispose() {
... ...
/// BaseStatefulPageState的子类,重写 dispose()
/// 一定要执行父类 dispose(),防止内存泄漏
super.dispose();
}
ValueNotifier<int> tapNum = ValueNotifier<int>(0);
@override
Widget appBuild(BuildContext context) {
... ...
}
/// 是否保存页面状态
@override
bool get wantKeepAlive => true;
}
ViewModel
class HomeViewModel extends PageViewModel<HomeViewState> {
@override
onCreate() {
/// 拿到 页面状态里的 对象、属性 等等
debugPrint('---runSwitchLogin:${state.runSwitchLogin}');
... ...
/// 初始化 网络请求
requestData();
}
@override
onDispose() {
... ...
/// 别忘了执行父类的 onDispose
super.onDispose();
}
/// 请求数据
@override
Future<PageViewModel?> requestData({Map<String, dynamic>? params}) async {
... ...
}
}
网络请求
BaseRepository
typedef JsonCoverEntity<T extends BaseModel> = T Function(Map<String, dynamic> json);
class BaseRepository {
/// 统一处理 响应数据,可以避免写 重复代码,但如果业务复杂,可能还是需要在原始写法上,扩展
/// 普通页面(非分页)数据请求 统一处理
Future<PageViewModel> httpPageRequest({
required PageViewModel pageViewModel,
required Future<Response> future,
required JsonCoverEntity jsonCoverEntity,
CancelToken? cancelToken,
int curPage = 1,
}) async {
try {
Response response = await future;
if (response.statusCode == REQUEST_SUCCESS) {
/// 请求成功
pageViewModel.pageDataModel?.type = NotifierResultType.success;
/// ViewModel 和 Model 相互持有
dynamic model = jsonCoverEntity(response.data);
model.vm = pageViewModel;
pageViewModel.pageDataModel?.data = model;
} else {
/// 请求成功,但业务不通过,比如没有权限
pageViewModel.pageDataModel?.type = NotifierResultType.unauthorized;
pageViewModel.pageDataModel?.errorMsg = response.statusMessage;
}
} on DioException catch (dioEx) {
/// 请求异常
pageViewModel.pageDataModel?.type = NotifierResultType.dioError;
pageViewModel.pageDataModel?.errorMsg = dioErrorConversionText(dioEx);
} catch (e) {
/// 未知异常
pageViewModel.pageDataModel?.type = NotifierResultType.fail;
pageViewModel.pageDataModel?.errorMsg = e.toString();
}
return pageViewModel;
}
/// 分页数据请求 统一处理
Future<PageViewModel> httpPagingRequest({
required PageViewModel pageViewModel,
required Future<Response> future,
required JsonCoverEntity jsonCoverEntity,
CancelToken? cancelToken,
int curPage = 1,
}) async {
try {
Response response = await future;
if (response.statusCode == REQUEST_SUCCESS) {
/// 请求成功
pageViewModel.pageDataModel?.type = NotifierResultType.success;
/// 有分页
pageViewModel.pageDataModel?.isPaging = true;
/// 分页代码
/// ViewModel 和 Model 相互持有代码,写着 correlationPaging() 里面
pageViewModel.pageDataModel?.correlationPaging(
pageViewModel,
jsonCoverEntity(response.data) as dynamic,
);
} else {
/// 请求成功,但业务不通过,比如没有权限
pageViewModel.pageDataModel?.type = NotifierResultType.unauthorized;
pageViewModel.pageDataModel?.errorMsg = response.statusMessage;
}
} on DioException catch (dioEx) {
/// 请求异常
pageViewModel.pageDataModel?.type = NotifierResultType.dioError;
pageViewModel.pageDataModel?.errorMsg = dioErrorConversionText(dioEx);
} catch (e) {
/// 未知异常
pageViewModel.pageDataModel?.type = NotifierResultType.fail;
pageViewModel.pageDataModel?.errorMsg = e.toString();
}
return pageViewModel;
}
}
Get请求
class HomeRepository extends BaseRepository {
/// 获取首页数据
Future<PageViewModel> getHomeData({
required PageViewModel pageViewModel,
CancelToken? cancelToken,
int curPage = 1,
}) async =>
httpPageRequest(
pageViewModel: pageViewModel,
jsonCoverEntity: HomeListModel.fromJson,
future: DioClient().doGet('project/list/$curPage/json?cid=294', cancelToken: cancelToken),
cancelToken: cancelToken,
curPage: curPage);
/// 这是不使用 httpPageRequest 的原始写法,如果业务复杂,可能还是需要在原始写法上,扩展
// /// 获取首页数据
// Future<PageViewModel> getHomeData({
// required PageViewModel pageViewModel,
// CancelToken? cancelToken,
// int curPage = 1,
// }) async {
// try {
// Response response = await DioClient().doGet('project/list/$curPage/json?cid=294', cancelToken: cancelToken);
//
// if(response.statusCode == REQUEST_SUCCESS) {
// /// 请求成功
// pageViewModel.pageDataModel?.type = NotifierResultType.success;
//
// /// ViewModel 和 Model 相互持有
// HomeListModel model = HomeListModel.fromJson(response.data);
// model.vm = pageViewModel;
// pageViewModel.pageDataModel?.data = model;
// } else {
//
// /// 请求成功,但业务不通过,比如没有权限
// pageViewModel.pageDataModel?.type = NotifierResultType.unauthorized;
// pageViewModel.pageDataModel?.errorMsg = response.statusMessage;
// }
//
// } on DioException catch (dioEx) {
// /// 请求异常
// pageViewModel.pageDataModel?.type = NotifierResultType.dioError;
// pageViewModel.pageDataModel?.errorMsg = dioErrorConversionText(dioEx);
//
// } catch (e) {
// /// 未知异常
// pageViewModel.pageDataModel?.type = NotifierResultType.fail;
// pageViewModel.pageDataModel?.errorMsg = e.toString();
// }
//
// return pageViewModel;
// }
}
Post请求
class PersonalRepository extends BaseRepository {
/// 注册
Future<PageViewModel> registerUser({
required PageViewModel pageViewModel,
Map<String, dynamic>? params,
CancelToken? cancelToken,
}) async =>
httpPageRequest(
pageViewModel: pageViewModel,
cancelToken: cancelToken,
jsonCoverEntity: UserInfoModel.fromJson,
future: DioClient().doPost(
'user/register',
params: params,
cancelToken: cancelToken,
));
/// 登陆
Future<PageViewModel> loginUser({
required PageViewModel pageViewModel,
Map<String, dynamic>? params,
CancelToken? cancelToken,
}) async =>
httpPageRequest(
pageViewModel: pageViewModel,
cancelToken: cancelToken,
jsonCoverEntity: UserInfoModel.fromJson,
future: DioClient().doPost(
'user/login',
params: params,
cancelToken: cancelToken,
));
/// 这是不使用 httpPageRequest 的原始写法,如果业务复杂,可能还是需要在原始写法上,扩展
// /// 注册
// Future<PageViewModel> registerUser({
// required PageViewModel pageViewModel,
// Map<String, dynamic>? params,
// CancelToken? cancelToken,
// }) async {
//
// try {
// Response response = await DioClient().doPost(
// 'user/register',
// params: params,
// cancelToken: cancelToken,
// );
//
// if(response.status