Flutter MVVM 开发范式

基于Provider状态管理用来实现MVVM框架,采用的是 ViewModel 继承于 ChangeNotifier,数据请求,数据处理在ViewModel中进行处理,然后再通过 ChangeNotifierProvider 提供给子Widget数据,ViewModel数据刷新通过调用 notifyListeners() 来通知Widget进行刷新,Widget 通过 Provider.of 、Consumer、Selector 来监听数据变化重新 build 从而达到更新UI的目的

1.文件结构

建对应项目的总文件目录,下面放api(存放数据请求),model(模型),viewModel(数据处理),widget(该项目公共或封装的控件),page(页面)

346E0782-6BDE-45D4-BEE5-29E8265A37D5.png

可以新建一个page文件,将页面都放在page文件里面

2.构建viewModel文件,继承于 ChangeNotifier,使用provider进行状态管理

在viewModel中声明要用到的属性,声明数据状态改变之后界面会刷新的方法,同时在里面进行网络请求管理,获取请求数据,在回调中处理对应的请求数据,在需要刷新界面的地方调用notifyListeners()方法

class FeedbackHomeViewModel extends ChangeNotifier{
  FeedbackList processingList;
  FeedbackList resolvedList;

  int _processPage = 0;
  int _resolvedPage = 0;

  List<FeedbackType> typeList;

  String customerServiceTel;

  bool hasMore(int status) {
    return status == STATUS_PROCESSING
        ? (_processPage * 10 < (processingList?.total ?? 0))
        : (_resolvedPage * 10 < (resolvedList?.total ?? 0));
  }

  Future refresh(int status) {
    status == STATUS_PROCESSING ? _processPage = 0 : _resolvedPage = 0;
    return _fetchFeedbackList(status, pageNo: 1).then((_) {
      status == STATUS_PROCESSING ? _processPage ++ : _resolvedPage ++;
    });
  }

  Future loadMore(int status) {
    var page = status == STATUS_PROCESSING ? _processPage : _resolvedPage;
    return _fetchFeedbackList(status, pageNo: page + 1).then((_) {
      status == STATUS_PROCESSING ? _processPage ++ : _resolvedPage ++;
    });
  }

  Future _fetchFeedbackList(int status, {int pageNo = 1, int pageSize = 10}) {
    return FeedbackApi.fetchFeedbackList(status, pageNo: pageNo, pageSize: pageSize)
      .then((json) {
        var resultList = FeedbackList.fromJson(json);
        var feedbackList = status == STATUS_PROCESSING ? processingList : resolvedList;

        if(pageNo > 1) {
          resultList.data.insertAll(0, feedbackList.data);
        }

        status == STATUS_PROCESSING ? processingList = resultList : resolvedList = resultList;

        notifyListeners();
    }).catchError((error) {
      print(error);
    });
  }

  Future fetchHotLine() {
    return FeedbackApi.fetchHotline().then((value) {
      customerServiceTel = HotLine.fromJson(value).customerServiceTel;
      notifyListeners();
    }).catchError((error) => print(error));
  }

  void fetchFeedbackType() async {
    return FeedbackApi.fetchFeedbackType()
        .then((json) {
          typeList = json.map<FeedbackType>((value) => FeedbackType.fromJson(value)).toList();
          notifyListeners();
    });
  }
}

如果provider数据需要从page页面传入的时候,或者是本地数据需要进行修改的时候,同样声明私有,可以声明一个对应的方法,修改属性值。

class CounterModel with ChangeNotifier {
  int _count = 0;
  int get value => _count;
    // // ///选中的执行人
  TaskEmployeModel _taskSelectExecutor;

  void increment() {
    _count++;
    notifyListeners();
  }

  TaskEmployeModel get taskSelectExecutor => _taskSelectExecutor;
  set taskSelectExecutor(TaskEmployeModel value) {
    _taskSelectExecutor = value;
    notifyListeners();
  }

}

3.page界面的使用

初始化并获取请求数据

final _viewModel = FeedbackHomeViewModel();

  @override
  void initState() {
    super.initState();
    _viewModel.fetchFeedbackType();
    _viewModel.fetchHotLine();
    _viewModel.refresh(STATUS_PROCESSING);
    _viewModel.refresh(STATUS_RESOLVED);
  }

在创建页面的时候,采用provider进行管理,create传入对应的viewmodel

 Widget build(BuildContext context) {
    return ChangeNotifierProvider(
        create: (_) => _viewModel,
        child: Scaffold(
            body: Container()));
  }

读状态的时候有Consumer和Selector模式,Consumer是监听provider中的所有数据,只要provider中的数据发生了改变,就会重新进行build,然后刷新界面;Selector可以更加细致化的监听provider中对应的具体的数据,只有当对应的具体数据发生改变的时候,才会重新build,刷新界面,这边推荐使用Selector来读取数据,更加细致化的管理,降低无效build次数

Consumer获取数据

Consumer<WarehouseFilterMenuProvider>(
        builder: (_, provider, __) {
          return Column(
            children: <Widget>[
              Expanded(
                child: ListView.builder(
                    itemCount: provider?.warehouseList?.length ?? 0,
                    itemBuilder: (BuildContext context, int index) {
                      var brand = provider.warehouseList[index];
                      return ListTile(
                        onTap: () {
                          widget.onSelected(brand);
                        },
                        title: Text(brand.warehouseName),
                      );
                    }),
              ),
            ],
          );
        },
      ),

Selector读取数据

其中Selector中的<>需要指定对应的provider和监听的数据类型,否则会报错

     Selector<TaskCreateProvider, TaskCreateType>(
            builder: (_, value, __) => _buildTaskType(context),
            selector: (_, taskCreateProvider) =>
                taskCreateProvider.taskSelectType),

修改状态

 FloatingActionButton(
    onPressed: () =>
        Provider.of<CounterModel>(context, listen: false).increment(),
  ),

 onRefresh: () async {
     Provider.of<FeedbackHomeViewModel>(context, listen: false)
     .refresh(status);
 },

多个provider的时候,在build初始化的时候,可以选择使用MultiProvider进行管理操作

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
        providers: [
          ChangeNotifierProvider<A>(
            create: (context) => model1,
          ),
          ChangeNotifierProvider<B>(
            create: (context) => model2,
          )
        ],
        child: Consumer2<A, B>(
          builder: widget.builder,
          child: widget.child,
        ));
  }
}

其中model1 和model2 是对应的provider管理,Consumer2和Selector2,支持两个provider,最多能支持到6个

这样使用了MVVM模式,在page页面只要使用对应的数据即可,在viewModel中将对应的数据处理好,或者提供改变数据状态的方法,是的可以更好的管理数据状态,更加的清晰易读,在复杂的场景的可以更好的处理逻辑

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

推荐阅读更多精彩内容