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(),
  };
}
上一篇下一篇

猜你喜欢

热点阅读