Flutter的VIPER架构

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(),
  };
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容