Flutter的VIPER架构
2023-09-04 本文已影响0人
oldSix_Zhu
在Flutter项目模块化架构搭建这篇文章里有朋友对flutter的viper感兴趣,搞了个demo出来。
其实viper也就是在mvp基础上把m继续拆分为i和e,把router也抽出来统一处理。
感觉最重要的思想是面向接口编程。
所以会有个protocol文件来定义该模块的方法,让各层实现,协议接口想叫啥都行项目里统一就好。
在页面开始先写这个协议,然后各层implements实现协议,根据AS的爆红提示直接点击override方法,非常清晰。
V(view,页面)
I(interactor,接口,后台网络请求)
P(presenter,业务逻辑处理、跳转等)
E(entity,就是纯数据模型,负责接口字段对应而已)
R(router,对外跳转接口,这个每个小业务不用再实现了,由整个模块的Router负责)
Don't bb, show me the code.
下面看下面这个很简单的demo,1个页面,2个模型,2个接口,3个展示数据用的Text。
我用到了getx做数据绑定,用到的地方我都有标注释,不想看删掉就能更简洁。
mine_home_protocol.dart
import 'model/mine_detail_entity.dart';
import 'model/mine_setting_entity.dart';
import 'package:get/get.dart';
// 使用StateMixin
class MineSettingEntityController extends GetxController with StateMixin<MineSettingEntity> {
}
abstract class MineHomeViewProtocol {
/// 获取详情成功
void p2vGetDetailDataSuccess();
/// 获取详情失败
void p2vGetDetailDataFail(Error error);
/// 获取设置成功
void p2vGetSettingDataSuccess();
/// 获取设置失败
void p2vGetSettingDataFail(Error error);
}
abstract class MineHomePresenterProtocol {
// 这里数据持有有2种方式,一种是模型,一种是使用GetxController的StateMixin
MineDetailEntity get detailEntity;
MineSettingEntity get settingEntity;
// 使用StateMixin
MineSettingEntityController get settingController;
/// 获取详情
void v2pGetDetailData();
/// 获取设置
void v2pGetSettingData();
/// 获取详情成功
void m2pGetDetailDataSuccess(MineDetailEntity detailEntity);
/// 获取详情失败
void m2pGetDetailDataFail(Error error);
}
abstract class MineHomeInteractorProtocol {
// 这里接口的实现有2种方式,一种是把成功和失败结果分别返回到p层,一种是把Future直接返回到p层处理
// 第一种方法会比较多,但是适合数据处理比较麻烦的接口,各有优劣吧
/// 获取详情
void p2iGetDetailData();
/// 获取设置
Future<Map<String, dynamic>> p2iGetSettingData();
}
mine_home_page.dart
import 'package:flutter/material.dart';
import '../mine_home_protocol.dart';
import '../presenter/mine_home_presenter.dart';
import 'package:get/get.dart';
class MineHomePage extends StatelessWidget implements MineHomeViewProtocol {
late MineHomePresenterProtocol iPresenter;
@override
Widget build(BuildContext context) {
// 入口绑定p层,如果用StatefulWidget在initState中做绑定
iPresenter = MineHomePresenter(this);
iPresenter.v2pGetDetailData();
iPresenter.v2pGetSettingData();
return Scaffold(
appBar: AppBar(
title: Text("我的"),
),
body: Center(
child: Column(
children: [
SizedBox(height: 100),
Obx(() {
return Text(iPresenter.detailEntity.accountName ?? '- -');
}),
SizedBox(height: 50),
Obx(() {
return Text(
iPresenter.settingEntity.noticeType == 1 ? '开' : '关',
style: TextStyle(
color: Colors.blue,
),
);
}),
SizedBox(height: 50),
// 使用StateMixin
iPresenter.settingController.obx(
(value) {
return Text(
value?.noticeType == 1 ? '开' : '关',
style: TextStyle(
color: Colors.green,
),
);
},
onLoading: const Center(child: CircularProgressIndicator()),
onEmpty: const Text('暂无数据'),
onError: (error) {
return Text(error ?? '未知错误');
},
),
],
),
),
);
}
@override
void p2vGetDetailDataFail(Error error) {
// 弹窗提示,错误展示
}
@override
void p2vGetDetailDataSuccess() {
// 如果用StatefulWidget可以setState
}
@override
void p2vGetSettingDataFail(Error error) {
// 弹窗提示,错误展示
}
@override
void p2vGetSettingDataSuccess() {
// 如果用StatefulWidget可以setState
}
}
mine_home_presenter.dart
import '../mine_home_protocol.dart';
import '../model/mine_home_interactor.dart';
import '../model/mine_setting_entity.dart';
import '../model/mine_detail_entity.dart';
import 'package:get/get.dart';
class MineHomePresenter implements MineHomePresenterProtocol {
late MineHomeInteractorProtocol iInteractor;
late MineHomeViewProtocol iView;
final _detailObs = MineDetailEntity().obs;
final _settingObs = MineSettingEntity().obs;
// 使用StateMixin
final MineSettingEntityController _settingController = Get.put(MineSettingEntityController());
// 构造函数绑定v层i层
MineHomePresenter(MineHomeViewProtocol view) {
iView = view;
iInteractor = MineHomeInteractor(this);
}
@override
void v2pGetDetailData() {
iInteractor.p2iGetDetailData();
}
@override
void m2pGetDetailDataSuccess(MineDetailEntity detailEntity) {
_detailObs.value = detailEntity;
// 用getx监听更新就可以不用主动告诉view层
//iView.p2vGetDetailDataSuccess();
}
@override
void m2pGetDetailDataFail(Error error) {
iView.p2vGetDetailDataFail(error);
}
@override
void v2pGetSettingData() {
_settingController.change(null, status: RxStatus.loading());
iInteractor.p2iGetSettingData().then((value) {
MineSettingEntity settingEntity = MineSettingEntity.fromJson(value);
_settingObs.value = settingEntity;
// 使用StateMixin
_settingController.change(settingEntity, status: RxStatus.success());
// 用getx监听更新就可以不用主动告诉view层
//iView.p2vGetSettingDataSuccess();
}).catchError((error) {
_settingController.change(null, status: RxStatus.error('获取设置信息失败'));
// iView.p2vGetSettingDataFail(error);
});
}
@override
MineDetailEntity get detailEntity => _detailObs.value;
@override
MineSettingEntity get settingEntity => _settingObs.value;
@override
MineSettingEntityController get settingController => _settingController;
}
mine_home_interactor.dart
import 'mine_detail_entity.dart';
import '../mine_home_protocol.dart';
class MineHomeInteractor implements MineHomeInteractorProtocol {
late MineHomePresenterProtocol iPresenter;
// 构造函数绑定p层
MineHomeInteractor(this.iPresenter);
@override
void p2iGetDetailData() {
Future.delayed(Duration(seconds: 3),(){
var map = <String, dynamic>{};
map["accountId"] = '111111';
map['accountPhone'] = '18812345678';
map["accountGender"] = 1;
map["accountName"] = '哈哈哈';
MineDetailEntity detailEntity = MineDetailEntity.fromJson(map);
iPresenter.m2pGetDetailDataSuccess(detailEntity);
});
}
@override
Future<Map<String, dynamic>> p2iGetSettingData() async {
var map = <String, dynamic>{};
await Future.delayed(Duration(seconds: 3),(){
map["noticeType"] = 1;
map["languageType"] = 1;
map["themeType"] = 1;
});
return map;
}
}
mine_detail_entity.dart
class MineDetailEntity {
/// id
String? accountId;
/// 电话号码
String? accountPhone;
/// 性别
int? accountGender;
/// 姓名
String? accountName;
MineDetailEntity({
this.accountId,
this.accountPhone,
this.accountGender,
this.accountName,
});
MineDetailEntity.fromJson(Map<String, dynamic> json) {
accountId = json['accountId'];
accountPhone = json['accountPhone'];
accountGender = json['accountGender'];
accountName = json['accountName'];
}
Map<String, dynamic> toJson() {
var map = <String, dynamic>{};
map["accountId"] = accountId;
map['accountPhone'] = accountPhone;
map["accountGender"] = accountGender;
map["accountName"] = accountName;
return map;
}
}
mine_setting_entity.dart
class MineSettingEntity {
/// 通知开关
int? noticeType;
/// 语言
int? languageType;
/// 主题
int? themeType;
MineSettingEntity({
this.noticeType,
this.languageType,
this.themeType,
});
MineSettingEntity.fromJson(Map<String, dynamic> json) {
noticeType = json['noticeType'];
languageType = json['languageType'];
themeType = json['themeType'];
}
Map<String, dynamic> toJson() {
var map = <String, dynamic>{};
map["noticeType"] = noticeType;
map["languageType"] = languageType;
map["themeType"] = themeType;
return map;
}
}
mine_router.dart
import 'package:flutter/cupertino.dart';
import 'home/view/mine_home_page.dart';
class MineRouter {
static const ROUTE_MINE_HOME = '/demo_mine/mine_home';
static Map<String, WidgetBuilder> routes = {
ROUTE_MINE_HOME : (context) => MineHomePage(),
};
}