前言

如果对Consumer很了解的同学可以继续学习 Flutter Provider状态管理 - Selector

个人觉得Flutter的学习有三个很重要的阶段

  1. widget的学习和使用
  2. 数据以及状态的管理
  3. 和原生的交互

对于第一点不必多说,大家开始学习flutter的时候都是跟着官网或者博客来学习如何使用widget以及用widget组合成丰富多彩的界面。如果还没有学习的同学可以前往Flutter中文网开始学习。

Widget的学习比较简单,即便是不太熟悉的widget我们只需要查看对应的构造方法和属性的注释大概就能搞懂80%,而且我们写页面就像是写配置文件一样,然后交由flutter底层去处理就好了。但是我们有了界面还得有数据呀,flutter数据的管理是一门学问,很多同学刚开始用flutter接入数据的时候完全不知道数据该放在那里,整个项目用什么框架等等问题。。。。一开始我也遇到这个问题,看到网上说闲鱼的fish-redux框架很不错,把数据和UI完全隔离开了,而且采用的是数据驱动UI的方式(类似于MVVM框架),但作为一个redux框架的小白当我去学习fish-redux的时候我一脸懵逼。。。。。。

这玩意也太复杂了吧,拆分的粒度超细致,大爷我一个类就能实现的东西你给我搞五六个出来,还说什么复用。。。。。况且我现在写的都是小demo呀,用这个岂不是杀鸡焉用宰牛刀(郑重声明:不否认fish-redux的牛逼之处,大项目使用可能真的有奇效

好在还有Provider,这框架怎么说也是google官方推荐的,不会有差!

再说一下为什么要使用状态管理:

我们都知道在flutter中要改变页面非常简单,只需要setState就可以了,但是调用这个方法的代码是极其昂贵的,会导致我们整个页面的重绘,简单的“计数器”就不说了,即便是一直setState我们也不太会感知页面的卡顿,但是如果我们把页面换成“淘宝”,“一东”这种复杂的页面呢?你一个按钮文字的变化都会导致整个页面的重绘,代码是不是太大了。。。

话不多说,我们来看看Provider的使用:

1.引包

dependencies:provider: ^3.1.0+1

多说一句:强烈建议使用3.1.0以后的版本,至于原因嘛,后续有时间会提到

2.采用最简单的计数器代码来整合provider

一共有三个文件

2.1 main.dart 这个不用多说,程序的入口


class MyApp extends StatelessWidget {// This widget is the root of your application.@overrideWidget build(BuildContext context) {///这里用到了MultiProvider,针对多个Provider的使用场景///在这里我们初始化了CounterProvider并且指定了child为MaterialAppreturn MultiProvider(providers: [ChangeNotifierProvider(builder: (_) => CounterProvider())],child: MaterialApp(title: 'Flutter Demo',theme: ThemeData(// This is the theme of your application.//// Try running your application with "flutter run". You'll see the// application has a blue toolbar. Then, without quitting the app, try// changing the primarySwatch below to Colors.green and then invoke// "hot reload" (press "r" in the console where you ran "flutter run",// or simply save your changes to "hot reload" in a Flutter IDE).// Notice that the counter didn't reset back to zero; the application// is not restarted.primarySwatch: Colors.blue,),routes: {Routes.INDEX_PAGE: (context) => IndexPage(),Routes.STOCK_LIST_PAGE: (context) => StockListPage(),Routes.QRCODE_SCAN_PAGE: (context) => QrCodeScanPage(),Routes.LOGIN: (context) => LoginPage(),Routes.SPLASH: (context) => SplashPage(),Routes.MY_APP: (context) => MyPage()},initialRoute: Routes.MY_APP,),);}
}

在程序的入口我们用MultiProvider包裹了一层,而且初始化了CounterProvider,把child指定为MaterialApp。

注:不建议在程序入口初始化Provider,这里只是为了演示方便这么做,实际项目中要是都在程序入口初始化可能会导致内存急剧增加,除非是共享一些全局的状态,例如app日夜间模式切换,中英文切换等。。。。

2.2 CounterProvider

class CounterProvider with ChangeNotifier {int _count = 0;int get value => _count;void increment() {_count++;notifyListeners();}
}

这个类做了三件事:

  1. 继承自ChangeNotifier
  2. 初始化count为0(这也是我们要提供出去的数据),并提供一个get方法
  3. 声明一个increment函数来改变count的值,并且每次改变都会触发notifyListeners(),这个方法的作用是通知CounterProvider的宿主我的值已经改变了

2.3 my_page.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';import 'counter_provider.dart';class MyPage extends StatefulWidget {@overrideState<StatefulWidget> createState() => MyPageState();
}class MyPageState extends State<MyPage> {@overrideWidget build(BuildContext context) {//获取CounterProviderCounterProvider counterProvider = Provider.of<CounterProvider>(context);print('页面重绘了。。。。。。。。。。。');return Scaffold(appBar: AppBar(title: Text('my page'),),body: Center(child: Text(//获取数据'value: ${counterProvider.value}',style: TextStyle(fontSize: 20),)),floatingActionButton: FloatingActionButton(child: Icon(Icons.navigation),onPressed: () {//调用increment方法改变数据counterProvider.increment();}),);}
}

啊哈哈,终于到UI了,看到了我们熟悉的Widget, 代码很简单,主要是三步:

  1. 在build方法里获取到在程序入口(main.dart)初始化的CountProvider
  2. 点击按钮调用increment方法,改变了count的值
  3. 在text中获取count的值

由于Provider也是数据驱动UI,所以我们只需要把值填上去,UI就能自动刷新。经过测试发现确实达到了我们预期的效果,每点击一次按钮text的数值都会加一。

但不要忘了我们一开始说的:我们用状态管理最重要的是解决整个页面重绘的问题!

在上面的demo中我们只希望Text这个widget重绘,毕竟它的值是改变了,但是按钮以及appbar我们是不希望重绘的,所以我们加了一句print语句,如果我每次点击按钮都会打印“页面重绘了。。。。。。”那就是一件很蛋疼的事,那事实是怎样呢?

真TM打印了,那我们用Provider还有diao用?

先别急,听我慢慢说来:

我们在代码中是采用Provider.of来获取CounterProvider,这中获取方式确实会引起整个页面的重绘,至于原因不在本章讨论范围,以后有时间再说。 那么Provider到底能不能实现“局部刷新”呢? 当然是可以的,不然这个框架真的没啥用了。下面我们再来认识一位重量级嘉宾:

3.Cosumer

这一节我们沿用计数器的代码,对其进行改造。之前我们提到了在程序入口初始化Provider是很不规范的,所以我们改成在页面级别初始化,并结合Consumer来使用。所以我们就剩下两个文件了,CountProvider和MyPage

3.1 CountProvider

和之前的一模一样,没改!!!!

3.2 MyPage

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';import 'counter_provider.dart';class MyPage extends StatefulWidget {@overrideState<StatefulWidget> createState() => MyPageState();
}class MyPageState extends State<MyPage> {///初始化CounterProviderCounterProvider _counterProvider = new CounterProvider();@overrideWidget build(BuildContext context) {print('页面重绘了。。。。。。。。。。。');//整个页面使用ChangeNotifier来包裹return ChangeNotifierProvider(builder: (context) => _counterProvider,child://child里面的内容不会因为数据的改变而重绘Scaffold(appBar: AppBar(title: Text('my page'),),body: Center(child://使用Cousumer来获取ProviderConsumer(builder: (BuildContext context,CounterProvider counterProvider, Widget child) {print('Text重绘了。。。。。。');return Text(//获取数据'value: ${counterProvider.value}',style: TextStyle(fontSize: 20),);})),floatingActionButton: FloatingActionButton(child: Icon(Icons.navigation),onPressed: () {//调用increment方法改变数据_counterProvider.increment();}),),);}
}

稍微复杂了点,请耐心看下去

  1. 我们把CounterProvider的初始化提到了MyPageState
  2. 整个页面用ChangeNotifierProvider来包裹,并指定数据源是CounterProvider
  3. 摒弃之前获取Provider的方式,使用Consumer来获取

先说结论:用上面的代码去运行并不会导致整个页面的重绘,仅仅是Text才会重绘,不信你看:

然后我们就要说几个有疑问的地方了

1.使用MultiProvider和ChangeNotifierProvider来初始化有什么不同?

const MultiProvider({Key key,//list@required this.providers,this.child,})  : assert(providers != null),super(key: key);
ChangeNotifierProvider({Key key,@required ValueBuilder<T> builder,Widget child,}) : super(key: key, builder: builder, dispose: _disposer, child: child);

MultiProvider接收的参数是List,如果当前页面的数据来自多个Provider就使用它来初始化,比如我们在“我的”页面可能要显示用户信息(UserProvider)以及订单信息(OrderProvider)

ChangeNotifierProvider就简单了,指定单个Provider而已

2.ChangeNotifierProvider和ChangeNotifierProvider.value的区别

  ChangeNotifierProvider({Key key,@required ValueBuilder<T> builder,Widget child,}) : super(key: key, builder: builder, dispose: _disposer, child: child);/// Provides an existing [ChangeNotifier].ChangeNotifierProvider.value({Key key,@required T value,Widget child,}) : super.value(key: key, value: value, child: child);
}

ChangeNotifierProvider(builder模式)的父类构造器多了一个disposer,当ChangeNotifierProvider从widget树中被移除时会自动调用dispose方法移除相应的数据,使得内存占用永远保持着一个合理的水平。

ChangeNotifierProvider.value在被移除widget树的时候不会自动调用dispose,需要手动去管理数据,这种方式适合一些老手,比如在被移除的时候依然有其它地方想使用这个数据,并在合适的时候再去手动关闭。

3.ChangeNotifierProvider中child参数下的widget不会因为数据的改变而重绘页面

上面的代码中我们把所有与UI相关的widget都放到了child下面,为什么只有Text发生了改变? 因为我们使用了Consumer来包裹,这样除开Consumer以外的内容都不会重绘,这也是为什么Provider能做到局部刷新UI。

所以我们在使用Consumer包裹内容的时候粒度要尽可能的细,要是直接包裹了全局那相当于没用Provider.

4.如何使用Consumer获取多个Provider

我们在计数器的例子中使用Consumer只获取了CounterProvider来给Text提供数据,但实际开发中我们的业务场景肯定会很复杂,使用Consumer包裹的widget需要多个Provider来提供数据咋办? 比如我们的Text需要显示count(来自CounterProvider),还有username(来自UserProvider)

Provider的作者当然也考虑到这一点,所以提供了多种Consumer来适应不同的场景

从Consumer——Consumer6,你最多可以一次性获取6个Provider,如果你要获取7个呢?抱歉,真没这种方式了,只能自力更生。。。。。

其实我也觉得这种实现方式不够优雅,但是6种已经能够满足99%的使用场景了!

掌握Consumer还不够,你需要 Flutter Provider状态管理 - Selector ,二者结合更高效!

结语:对于不熟悉Provider的同学,虽然上面讲的例子很简单,可能你也看懂了,但是请务必实际操作一番,里面有很多坑是需要自己去摸索的,这样在实际应用中才能游刃有余

如果有不懂的地方请留言,错误之处欢迎指正

Flutter Provider状态管理-Consumer相关推荐

  1. Flutter Provider状态管理---八种提供者使用分析

    Provider Provider是最基本的Provider组件,可以使用它为组件树中的任何位置提供值,但是当该值更改的时候,它并不会更新UI class UserModel {String name ...

  2. flutter基于provider状态管理设置主题颜色、实现简单登录、注册功能---页面+逻辑

    一.provider状态管理设置主题颜色 第一步: 安装依赖库 provider: ^4.3.2+3 第二步: 创建共享数据模型 import 'package:flutter/material.da ...

  3. Flutter GetX 状态管理 响应式编程(三)

    在码农的世界里,优美的应用体验,来源于程序员对细节的处理以及自我要求的境界,年轻人也是忙忙碌碌的码农中一员,每天.每周,都会留下一些脚印,就是这些创作的内容,有一种执着,就是不知为什么,如果你迷茫,不 ...

  4. Flutter GetX 状态管理 使用入门 程序计数器 (二)

    在码农的世界里,优美的应用体验,来源于程序员对细节的处理以及自我要求的境界,年轻人也是忙忙碌碌的码农中一员,每天.每周,都会留下一些脚印,就是这些创作的内容,有一种执着,就是不知为什么,如果你迷茫,不 ...

  5. Flutter - flutter_bloc状态管理

    继上一篇写了Flutter - GetX状态管理,会发现其实Flutter的状态管理的框架还是比较多的,用的比较多的有flutter_bloc.MobX.GetX等,今天我就来谈一谈我学习Flutte ...

  6. Flutter Provider 异步通信、Provider状态管理

    题记 -- 执剑天涯,从你的点滴积累开始,所及之处,必精益求精. Flutter是谷歌推出的最新的移动开发框架. [x1]微信公众号的每日提醒 随时随记 每日积累 随心而过 [x2]各种系列的视频教程 ...

  7. Provider状态管理练习

    一.添加依赖 provider: 6.0.5 二.创建CountModel.dart 注意:要继承ChangeNotifier import 'package:flutter/cupertino.da ...

  8. Flutter 对状态管理的认知与思考

    前言 关于这篇文章的一些内容,我很久之前就想写的,但一直没啥源动力,就一直鸽着 这次被捷特大佬催了几次,终于把这篇文章写完了,文章里有我对状态管理的一些思考和看法,希望能引起茫茫人海中零星的共鸣... ...

  9. Flutter _ 状态管理指南篇,Android开发两年

    ),); 然后我们开始测试,点击收藏按钮,查看 rebuild 情况. Performing hot reload- Syncing files to device iPhone Xs Max- - ...

最新文章

  1. 孩子、老人与海豚,如何用 AI 伴他们走出孤独
  2. 一行命令,瞬间从“马赛克”到高清影像
  3. LINUX 硬链接与软链接的区别
  4. LINQTOSQL作为底层ORM框架后,我们的数据基类就变成了这个样子
  5. linux:date 命令
  6. 游戏用户体验指标_电子游戏如何超越游戏化的用户体验
  7. [SpringSecurity]web权限方案_用户认证_查询数据库完成认证
  8. Java Integer类shortValue()方法与示例
  9. sharepoint SPFolder的使用
  10. Java学习笔记之设计模式(3)抽象工厂模式
  11. Java 算法SM2加密解密
  12. 手机触摸pass测试软件,PASS——功效分析和样本量计算软件
  13. 自然语言处理——第一章 绪论
  14. Python chardet
  15. word文件转pdf转换器注册码
  16. Efficient Low-rank Multimodal Fusion with Modality-Specific Factors 论文
  17. 华三imc服务器型号,华三imcportal服务器常见错误分析报告.doc
  18. 20个大数据可视化大屏模板(评论区附源码)
  19. pageInfo的转化,do转vo
  20. 特征工程系列(一):特征工程的概念/特征的处理

热门文章

  1. 华为帐号助力金融服务体验 中信银行成鸿蒙生态先行者
  2. Ae试水~(待填坑)
  3. 页面置换算法之 LRU算法
  4. Spring Cloud学习(一) ZuulFilter 过滤器详解
  5. appium滑动操作(向上、向下、向左、向右滑动)
  6. mqtt消息推送中间件服务器软件评价
  7. linux创建运维账户流程,Linux运维养成记-账户与权限管理
  8. 规则 | 卖家速自查!淘宝网发起专项整治,“品牌不一致”无处藏身
  9. 百度地图查看导航记录,导航路线,记录驾驶路线
  10. 五一劳动节,给父母发个红包吧