在Flutter項(xiàng)目模塊化架構(gòu)搭建這篇文章里有朋友對(duì)flutter的viper感興趣,搞了個(gè)demo出來。
其實(shí)viper也就是在mvp基礎(chǔ)上把m繼續(xù)拆分為i和e,把router也抽出來統(tǒng)一處理。
感覺最重要的思想是面向接口編程。
所以會(huì)有個(gè)protocol文件來定義該模塊的方法,讓各層實(shí)現(xiàn),協(xié)議接口想叫啥都行項(xiàng)目里統(tǒng)一就好。
在頁面開始先寫這個(gè)協(xié)議,然后各層implements實(shí)現(xiàn)協(xié)議,根據(jù)AS的爆紅提示直接點(diǎn)擊override方法,非常清晰。
V(view,頁面)
I(interactor,接口,后臺(tái)網(wǎng)絡(luò)請(qǐng)求)
P(presenter,業(yè)務(wù)邏輯處理、跳轉(zhuǎn)等)
E(entity,就是純數(shù)據(jù)模型,負(fù)責(zé)接口字段對(duì)應(yīng)而已)
R(router,對(duì)外跳轉(zhuǎn)接口,這個(gè)每個(gè)小業(yè)務(wù)不用再實(shí)現(xiàn)了,由整個(gè)模塊的Router負(fù)責(zé))
Don't bb, show me the code.
下面看下面這個(gè)很簡單的demo,1個(gè)頁面,2個(gè)模型,2個(gè)接口,3個(gè)展示數(shù)據(jù)用的Text。
我用到了getx做數(shù)據(jù)綁定,用到的地方我都有標(biāo)注釋,不想看刪掉就能更簡潔。


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);
/// 獲取設(shè)置成功
void p2vGetSettingDataSuccess();
/// 獲取設(shè)置失敗
void p2vGetSettingDataFail(Error error);
}
abstract class MineHomePresenterProtocol {
// 這里數(shù)據(jù)持有有2種方式,一種是模型,一種是使用GetxController的StateMixin
MineDetailEntity get detailEntity;
MineSettingEntity get settingEntity;
// 使用StateMixin
MineSettingEntityController get settingController;
/// 獲取詳情
void v2pGetDetailData();
/// 獲取設(shè)置
void v2pGetSettingData();
/// 獲取詳情成功
void m2pGetDetailDataSuccess(MineDetailEntity detailEntity);
/// 獲取詳情失敗
void m2pGetDetailDataFail(Error error);
}
abstract class MineHomeInteractorProtocol {
// 這里接口的實(shí)現(xiàn)有2種方式,一種是把成功和失敗結(jié)果分別返回到p層,一種是把Future直接返回到p層處理
// 第一種方法會(huì)比較多,但是適合數(shù)據(jù)處理比較麻煩的接口,各有優(yōu)劣吧
/// 獲取詳情
void p2iGetDetailData();
/// 獲取設(shè)置
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 ? '開' : '關(guān)',
style: TextStyle(
color: Colors.blue,
),
);
}),
SizedBox(height: 50),
// 使用StateMixin
iPresenter.settingController.obx(
(value) {
return Text(
value?.noticeType == 1 ? '開' : '關(guān)',
style: TextStyle(
color: Colors.green,
),
);
},
onLoading: const Center(child: CircularProgressIndicator()),
onEmpty: const Text('暫無數(shù)據(jù)'),
onError: (error) {
return Text(error ?? '未知錯(cuò)誤');
},
),
],
),
),
);
}
@override
void p2vGetDetailDataFail(Error error) {
// 彈窗提示,錯(cuò)誤展示
}
@override
void p2vGetDetailDataSuccess() {
// 如果用StatefulWidget可以setState
}
@override
void p2vGetSettingDataFail(Error error) {
// 彈窗提示,錯(cuò)誤展示
}
@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());
// 構(gòu)造函數(shù)綁定v層i層
MineHomePresenter(MineHomeViewProtocol view) {
iView = view;
iInteractor = MineHomeInteractor(this);
}
@override
void v2pGetDetailData() {
iInteractor.p2iGetDetailData();
}
@override
void m2pGetDetailDataSuccess(MineDetailEntity detailEntity) {
_detailObs.value = detailEntity;
// 用getx監(jiān)聽更新就可以不用主動(dòng)告訴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監(jiān)聽更新就可以不用主動(dòng)告訴view層
//iView.p2vGetSettingDataSuccess();
}).catchError((error) {
_settingController.change(null, status: RxStatus.error('獲取設(shè)置信息失敗'));
// 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;
// 構(gòu)造函數(shù)綁定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;
/// 電話號(hào)碼
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 {
/// 通知開關(guān)
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(),
};
}