etw系统provider事件较多_Flutter状态管理provider的使用和封装

Flutter提供了InheritedWidget类,帮助我们处理父子组件之间的状态管理。provider是InheritedWidget的封装,让开发者易于使用和服用。但是初看provider的文档,有点让人头大:

73a6996f2a0df3e6d1036f05ad6d22a5.png

不是说provider是易于使用吗?我只想以一种的简单的方式管理状态,却给我这么多选择,到底我该选择哪个呢?选择困难症急的想薅头发。

使用

新建Futter项目,更改默认的计数器布局,效果如下:

a01c8d68c9b5bb23e1d706642c9d0984.png

点击FlatButton,更改应用程序的计数器状态,使计数器加1,前两行的text显示计数器状态最新值,FlatButton和两个text是不同部分的widget。

  1. 在的pubspec.yaml文件中依赖provider:
dependencies:  flutter:    sdk: flutter  provider: ^4.1.2
  1. 导入: import 'package:provider/provider.dart';

Provider

Provider是provider包中最基本的提供者widget类型。它可以给包括住的所有widget提供值,但是当该值改变时,并不会更新widget。

新增MyModel类,作为要让Provider提供出去的值,把计数器的数值counter声明到这里,并且更改计数值的方法也放在这里,点击按钮的时候,调用MyModel对象的incrementCounter(),延时2秒并更改counter:

class MyModel {    MyModel({this.counter=0});  int counter = 0;  Future incrementCounter() async {    await Future.delayed(Duration(seconds: 2));    counter++;    print(counter);  }}

在widget树的顶部包裹Provider小部件,将MyModel对象通过Provider提供给widget树。然后使用了两种获取Provider提供值的方式,在Column里:

  1. 先使用Provider.of(context)获取到MyModel对象的引用;
  2. 然后使用Consumer小部件获得对MyModel对象的引用;
class MyHomePage extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Provider(      create: (_) => MyModel(),      child: Scaffold(        appBar: AppBar(          title: Text('provider'),        ),        body: Column(          children: [            Builder(              builder: (context) {                // 获取到provider提供出来的值                MyModel _model = Provider.of(context);                return Container(                    margin: const EdgeInsets.only(top: 20),                    width: MediaQuery.of(context).size.width,                    padding: const EdgeInsets.all(20),                    alignment: Alignment.center,                    color: Colors.lightBlueAccent,                    child: Text('当前是:${_model.counter}'));              },            ),            Consumer(                // 获取到provider提供出来的值              builder: (context, model, child) {                return Container(                  margin: const EdgeInsets.only(top: 20),                  width: MediaQuery.of(context).size.width,                  padding: const EdgeInsets.all(20),                  alignment: Alignment.center,                  color: Colors.lightGreen,                  child: Text(                    '${model.counter}',                  ),                );              },            ),            Consumer(               // 获取到provider提供出来的值              builder: (context, model, child) {                return FlatButton(                    color: Colors.tealAccent,                    onPressed:model.incrementCounter,                    child: Icon(Icons.add));              },            ),          ],        ),      ),    );  }}

点击FlatButton,model调用incrementCounter()函数,计数值加1。但是并不会重建UI,因为该Provider小部件不会监听其提供的值的更改。

5ef4e096ac4b7444cc8f537d6c4fc18b.png

打印出计数值的变化

5c6eb990ef8a66f3e206ff7de966a4ec.png

ChangeNotifierProvider

与最基础的Provider小部件不同,ChangeNotifierProvider会监听其提供出去的模型对象中的更改。当有值更改后,它将重建下方所有的Consumer和使用Provider.of(context)监听并获取提供值的地方。

代码中更改Provider为ChangeNotifierProvider。MyModel混入ChangeNotifier(继承也一样)。然后更改counter之后调用notifyListeners(),这样ChangeNotifierProvider就会得到通知,并且Consumer和监听的地方将重建其小部件。

class MyHomePage extends StatelessWidget {  @override  Widget build(BuildContext context) {    return ChangeNotifierProvider(      create: (_) => MyModel(),      child: Scaffold(        appBar: AppBar(          title: Text('provider'),        ),        body: Column(          children: [            Builder(              builder: (context) {                MyModel _model = Provider.of(context);                return Container(                    margin: const EdgeInsets.only(top: 20),                    width: MediaQuery.of(context).size.width,                    padding: const EdgeInsets.all(20),                    alignment: Alignment.center,                    color: Colors.lightBlueAccent,                    child: Text('当前是:${_model.counter}'));              },            ),            Consumer(              builder: (context, model, child) {                return Container(                  margin: const EdgeInsets.only(top: 20),                  width: MediaQuery.of(context).size.width,                  padding: const EdgeInsets.all(20),                  alignment: Alignment.center,                  color: Colors.lightGreen,                  child: Text(                    '${model.counter}',                  ),                );              },            ),            Consumer(              builder: (context, model, child) {                return FlatButton(                    color: Colors.tealAccent,                    onPressed: model.incrementCounter,                    child: Icon(Icons.add));              },            ),          ],        ),      ),    );  }}class MyModel with ChangeNotifier{  //                                                incrementCounter() async {    await Future.delayed(Duration(seconds: 2));    counter++;    print(counter);    notifyListeners();  }}
8ca7d260f4a7f664d69561b5630383fc.png

每次点击,都会更改计数器的值,如果第一行的计数值是保留初始值,不更新呢?很简单,把Provider.of的监听器设置为false,这样更改后就不会重新构建第一行的text: MyModel _model = Provider.of(context,listen: false);

9f2e497bd3c98f66f5058ed2391b9ab6.png

FutureProvider

FutureProvider基本上只是普通FutureBuilder的包装。我们需要给它提供一些显示在UI中的初始数据,还要为它设置要提供值的Future。在Future完成的时候,FutureProvider会通知Consumer重建自己的小部件。

在下面的代码中,使用了一个counter为0的MyModel向UI提供一些初始数据,并且添加了一个Future函数,可在3秒后返回一个counter为1的MyModel。 和基类Provider一样,FutureProvider它不会监听模型本身内的任何更改。在下面的代码中依旧通过按钮点击事件使counter加1,但是对UI没有影响。

class MyHomePage extends StatelessWidget {  @override  Widget build(BuildContext context) {    return FutureProvider(      initialData: MyModel(counter: 0),      create: (context) => someAsyncFunctionToGetMyModel(),      child: Scaffold(        appBar: AppBar(          title: Text('provider'),        ),        body: Column(          children: [            Builder(              builder: (context) {                MyModel _model = Provider.of(context, listen: false);                return Container(                    margin: const EdgeInsets.only(top: 20),                    width: MediaQuery.of(context).size.width,                    padding: const EdgeInsets.all(20),                    alignment: Alignment.center,                    color: Colors.lightBlueAccent,                    child: Text('当前是:${_model.counter}'));              },            ),            Consumer(              builder: (context, model, child) {                return Container(                  margin: const EdgeInsets.only(top: 20),                  width: MediaQuery.of(context).size.width,                  padding: const EdgeInsets.all(20),                  alignment: Alignment.center,                  color: Colors.lightGreen,                  child: Text(                    '${model.counter}',                  ),                );              },            ),            Consumer(              builder: (context, model, child) {                return FlatButton(                    color: Colors.tealAccent,                    onPressed: model.incrementCounter,                    child: Icon(Icons.add));              },            ),          ],        ),      ),    );  }  Future someAsyncFunctionToGetMyModel() async {    //   incrementCounter() async {    await Future.delayed(Duration(seconds: 2));    counter++;    print(counter);    notifyListeners();  }}

FutureProvider通过设置的Future完成后会通知Consumer,重新build。但是,Future完成后,点击按钮也不会更新UI。

FutureProvider适用于没有刷新和变更的页面,和FutureBuilder一样的作用。

StreamProvider

StreamProvider基本上是StreamBuilder的包装,和上面的FutureProvider一样。不同的是StreamProvider提供的是流,FutureProvider需要的一个Future。

StreamProvider也不会监听model本身的变化。它仅监听流中的新事件:

class MyHomePage extends StatelessWidget {  @override  Widget build(BuildContext context) {    return StreamProvider(      initialData: MyModel(counter: 0),      create: (context) => getStreamOfMyModel(),      child: Scaffold(        appBar: AppBar(          title: Text('provider'),        ),        body: Column(          children: [            Builder(              builder: (context) {                MyModel _model = Provider.of(context, listen: false);                return Container(                    margin: const EdgeInsets.only(top: 20),                    width: MediaQuery.of(context).size.width,                    padding: const EdgeInsets.all(20),                    alignment: Alignment.center,                    color: Colors.lightBlueAccent,                    child: Text('当前是:${_model.counter}'));              },            ),            Consumer(              builder: (context, model, child) {                return Container(                  margin: const EdgeInsets.only(top: 20),                  width: MediaQuery.of(context).size.width,                  padding: const EdgeInsets.all(20),                  alignment: Alignment.center,                  color: Colors.lightGreen,                  child: Text(                    '${model.counter}',                  ),                );              },            ),            Consumer(              builder: (context, model, child) {                return FlatButton(                    color: Colors.tealAccent,                    onPressed: model.incrementCounter,                    child: Icon(Icons.add));              },            ),          ],        ),      ),    );  }  Stream getStreamOfMyModel() {    return Stream.periodic(        Duration(seconds: 1), (x) => MyModel(counter: x)).take(10);  }}class MyModel with ChangeNotifier {  //                                                incrementCounter() async {    await Future.delayed(Duration(seconds: 2));    counter++;    print(counter);    notifyListeners();  }}

给StreamProvider设置了一个每隔1秒更新一次的stream,ui上的计数值也是每隔一秒改变一次。但是点击按钮同样不会刷新ui。所以也可以认为是一个StreamBuilder。

ValueListenableProvider

ValueListenableProvider类似于ValueChange的封装,它的作用和ChangeNotifierProvider一样,在值改变的时候,会通知Consumer重新build,但是使用起来比ChangeNotifierProvider复杂,需要先用Provider提供MyModel给Consumer,然后把MyModel里的ValueNotifier给ValueListenableProvider:

class MyHomePage extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Provider(      create: (context) => MyModel(),      child: Consumer(        builder: (context, myModel, child) {          return ValueListenableProvider.value(            value: myModel.counter,            child: Scaffold(              appBar: AppBar(                title: Text('provider'),              ),              body: Column(                children: [                  Builder(                    builder: (context) {                      var count = Provider.of(context);                      return Container(                          margin: const EdgeInsets.only(top: 20),                          width: MediaQuery.of(context).size.width,                          padding: const EdgeInsets.all(20),                          alignment: Alignment.center,                          color: Colors.lightBlueAccent,                          child: Text('当前是:$count'));                    },                  ),                  Consumer(                    builder: (context, value, child) {                      return Container(                        margin: const EdgeInsets.only(top: 20),                        width: MediaQuery.of(context).size.width,                        padding: const EdgeInsets.all(20),                        alignment: Alignment.center,                        color: Colors.lightGreen,                        child: Text(                          '$value',                        ),                      );                    },                  ),                  Consumer(                    builder: (context, model, child) {                      return FlatButton(                          color: Colors.tealAccent,                          onPressed: model.incrementCounter,                          child: Icon(Icons.add));                    },                  ),                ],              ),            ),          );        }      ),    );  }}class MyModel {  ValueNotifier counter = ValueNotifier(0);  Future incrementCounter() async {    await Future.delayed(Duration(seconds: 2));    print(counter.value++);    counter.value = counter.value;  }}

ListenableProvider

ListenableProvider和ChangeNotifierProvider一样, 区别在于,如果Model是一个复杂模型ChangeNotifierProvider 会在你需要的时候,自动调用其 _disposer 方法,所以一般还是使用ChangeNotifierProvider即可。

class MyHomePage extends StatelessWidget {  @override  Widget build(BuildContext context) {    return ListenableProvider(      create: (context) => MyModel(),      child: Scaffold(        appBar: AppBar(          title: Text('provider'),        ),        body: Column(          children: [            Builder(              builder: (context) {                MyModel modol = Provider.of(context);                return Container(                    margin: const EdgeInsets.only(top: 20),                    width: MediaQuery.of(context).size.width,                    padding: const EdgeInsets.all(20),                    alignment: Alignment.center,                    color: Colors.lightBlueAccent,                    child: Text('当前是:${modol.counter}'));              },            ),            Consumer(              builder: (context, model, child) {                return Container(                  margin: const EdgeInsets.only(top: 20),                  width: MediaQuery.of(context).size.width,                  padding: const EdgeInsets.all(20),                  alignment: Alignment.center,                  color: Colors.lightGreen,                  child: Text(                    '${model.counter}',                  ),                );              },            ),            Consumer(              builder: (context, model, child) {                return FlatButton(                    color: Colors.tealAccent,                    onPressed: model.incrementCounter,                    child: Icon(Icons.add));              },            ),          ],        ),      ),    );  }}class MyModel with ChangeNotifier {  int counter = 0;  Future incrementCounter() async {    await Future.delayed(Duration(seconds: 2));    counter++;    notifyListeners();    print(counter);  }}

MultiProvider

上面的示例都仅使用了一个Model对象。如果需要提供第二种类型的Model对象,可以嵌套Provider。但是,嵌套迷之缩进,可读性低。这时候使用MultiProvider非常简洁,

我们改下上面的计数器,一般首页会有一个banner和列表。我们用上面的计数器模拟banner,下面的计数器模拟列表:

class MyHomePage extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MultiProvider(      providers: [        ChangeNotifierProvider(create: (context) => BannerModel()),        ChangeNotifierProvider(create: (context) => ListModel()),      ],      child: Scaffold(        appBar: AppBar(          title: Text('provider'),        ),        body: Column(          children: [            Builder(              builder: (context) {                BannerModel modol = Provider.of(context);                return Container(                    margin: const EdgeInsets.only(top: 20),                    width: MediaQuery.of(context).size.width,                    padding: const EdgeInsets.all(20),                    alignment: Alignment.center,                    color: Colors.lightBlueAccent,                    child: Text('当前Banner有几个:${modol.counter}'));              },            ),            Consumer(              builder: (context, model, child) {                return Container(                  margin: const EdgeInsets.only(top: 20),                  width: MediaQuery.of(context).size.width,                  padding: const EdgeInsets.all(20),                  alignment: Alignment.center,                  color: Colors.lightGreen,                  child: Text(                    '当前Banner有几个:${model.counter}',                  ),                );              },            ),            Consumer(              builder: (context, model, child) {                return FlatButton(                    color: Colors.tealAccent,                    onPressed: model.getBanner,                    child: Text("获取banner"));              },            ),            Consumer(              builder: (context, model, child) {                return FlatButton(                    color: Colors.tealAccent,                    onPressed: model.getList,                    child: Text("获取列表"));              },            ),          ],        ),      ),    );  }}class BannerModel with ChangeNotifier {  int counter = 0;  Future getBanner() async {    await Future.delayed(Duration(seconds: 2));    counter++;    notifyListeners();    print(counter);  }}class ListModel with ChangeNotifier {  int counter = 0;  Future getList() async {    await Future.delayed(Duration(seconds: 2));    counter++;    notifyListeners();    print(counter);  }}
383e7237a60804c68262c09cfd317926.png

按下banner按钮,就单独获取banner的数值,并更新banner的Consumer。列表的同理。

ProxyProvider

如果要提供两个Model,但是其中一个Model取决于另一个Model,在这种情况下,可以使用ProxyProvider。A ProxyProvider从一个Provider获取值,然后将其注入另一个Provider,

把上面的改下,比如的上传图片功能,需要先把图片提交到图片服务器,然后再把链接发送到后台服务器:

class MyHomePage extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MultiProvider(      providers: [        ChangeNotifierProvider(create: (context) => PicModel()),        ProxyProvider(          update: (context, myModel, anotherModel) => SubmitModel(myModel),        ),      ],      child: Scaffold(        appBar: AppBar(          title: Text('provider'),        ),        body: Column(          children: [            Builder(              builder: (context) {                PicModel modol = Provider.of(context);                return Container(                    margin: const EdgeInsets.only(top: 20),                    width: MediaQuery.of(context).size.width,                    padding: const EdgeInsets.all(20),                    alignment: Alignment.center,                    color: Colors.lightBlueAccent,                    child: Text('提交图片:${modol.counter}'));              },            ),            Consumer(              builder: (context, model, child) {                return FlatButton(                    color: Colors.tealAccent,                    onPressed: model.upLoadPic,                    child: Text("提交图片"));              },            ),            Consumer(              builder: (context, model, child) {                return FlatButton(                    color: Colors.tealAccent,                    onPressed: model.subMit,                    child: Text("提交"));              },            ),          ],        ),      ),    );  }}class PicModel with ChangeNotifier {  int counter = 0;  Future upLoadPic() async {    await Future.delayed(Duration(seconds: 2));    counter++;    notifyListeners();    print(counter);  }}class SubmitModel {  PicModel _model;  SubmitModel(this._model);  Future subMit() async {    await _model.upLoadPic();  }}

基于MVVM模式封装Provider

相信大家都已经理解provider的流程,如下图:

9ad20fd6c589b5122933482c97e53a2d.png

上面已经演示完了Provider的用法,在开发中,我们需要Model充当ViewModel,处理业务逻辑,但是每次都写样板代码的话也很麻烦,所以需要封装下,易于使用。

75f85918610f4959428f79c78cf9683e.png
class MyHomePage extends StatelessWidget {  @override  Widget build(BuildContext context) {    return ChangeNotifierProvider(      create: (BuildContext context) {        return LoginViewModel(loginServive: LoginServive());      },      child: Scaffold(        appBar: AppBar(          title: Text('provider'),        ),        body: Column(          children: [            Consumer(              builder: (context, model, child) {                return Text(model.info);              },            ),            Consumer(              builder: (context, model, child) {                return FlatButton(                    color: Colors.tealAccent,                    onPressed: () => model.login("pwd"),                    child: Text("登录"));              },            ),          ],        ),      ),    );  }}/// viewModelclass LoginViewModel extends ChangeNotifier {  LoginServive _loginServive;  String info = '请登录';  LoginViewModel({@required LoginServive loginServive})      : _loginServive = loginServive;  Future login(String pwd) async {    info = await _loginServive.login(pwd);    notifyListeners();  }}/// apiclass LoginServive {  static const String Login_path = 'xxxxxx';  Future login(String pwd) async {    return new Future.delayed(const Duration(seconds: 1), () => "登录成功");  }}

这种页面写法,基本每个页面都要,下面我们一步一步开始封装。

  1. 一般页面载入的时候会显示一个loading,然后加载成功展示数据,失败就展示失败页面,所以枚举一个页面状态:
enum ViewState { Loading, Success,Failure }
  1. ViewModel都会在页面状态属性改变后更新ui,通常会调用notifyListeners,把这一步移到BaseModel中:
class BaseModel extends ChangeNotifier {  ViewState _state = ViewState.Loading;  ViewState get state => _state;  void setState(ViewState viewState) {    _state = viewState;    notifyListeners();  }}
  1. 我们知道ui里需要ChangeNotifierProvider提供Model,并且用Consumer更新ui。因此我们也将其内置到BaseView中:
class BaseWidget extends StatefulWidget {  final Widget Function(BuildContext context, T value, Widget child) builder;  final T model;  final Widget child;  BaseWidget({Key key, this.model, this.builder, this.child}) : super(key: key);  @override  State createState() => _BaseWidgetState();}class _BaseWidgetState extends State> {  T model;  @override  void initState() {    model = widget.model;    super.initState();  }  @override  Widget build(BuildContext context) {    return ChangeNotifierProvider.value(      value: model,      child: Consumer(        builder: widget.builder,        child: widget.child,      ),    );  }}
  1. 有时候我们的页面数据只是局部更新,Consumer的child属性就是模型更改时不需要重建的UI,所以我们将需要更新的ui放在builder里,不需要更新的写在child里:
Consumer(  // Pass the login header as a prebuilt-static child  child: LoginHeader(controller: _controller),  builder: (context, model, child) => Scaffold(    ...    body: Column (      children: [//不更新的部分        child,        ...      ]    )
  1. 大多时候,我们已进入一个页面,就要获取数据,所以我们也把这个操作移入基类:
class BaseWidget extends StatefulWidget {final Function(T) onModelReady;... BaseWidget({   ...    this.onModelReady,  });  ...}...@overridevoid initState() {  model = widget.model;  if (widget.onModelReady != null) {    widget.onModelReady(model);  }  super.initState();}

现在,我们用封装的基类完成登录页面:

class MyHomePage extends StatelessWidget {  @override  Widget build(BuildContext context) {    return BaseWidget(      model: LoginViewModel(loginServive: LoginServive()),      builder: (context, model, child) => Scaffold(        appBar: AppBar(          title: Text('provider'),        ),        body: Column(          children: [            model.state == ViewState.Loading                ? Center(                    child: CircularProgressIndicator(),                  )                : Text(model.info),            FlatButton(                color: Colors.tealAccent,                onPressed: () => model.login("pwd"),                child: Text("登录")),          ],        ),      ),    );  }}/// viewModelclass LoginViewModel extends BaseModel {  LoginServive _loginServive;  String info = '请登录';  LoginViewModel({@required LoginServive loginServive})      : _loginServive = loginServive;  Future login(String pwd) async {    setState(ViewState.Loading);    info = await _loginServive.login(pwd);    setState(ViewState.Success);  }}/// apiclass LoginServive {  static const String Login_path = 'xxxxxx';  Future login(String pwd) async {    return new Future.delayed(const Duration(seconds: 1), () => "登录成功");  }}enum ViewState { Loading, Success, Failure, None }class BaseModel extends ChangeNotifier {  ViewState _state = ViewState.None;  ViewState get state => _state;  void setState(ViewState viewState) {    _state = viewState;    notifyListeners();  }}class BaseWidget extends StatefulWidget {  final Widget Function(BuildContext context, T model, Widget child) builder;  final T model;  final Widget child;  final Function(T) onModelReady;  BaseWidget({    Key key,    this.builder,    this.model,    this.child,    this.onModelReady,  }) : super(key: key);  _BaseWidgetState createState() => _BaseWidgetState();}class _BaseWidgetState extends State> {  T model;  @override  void initState() {    model = widget.model;    if (widget.onModelReady != null) {      widget.onModelReady(model);    }    super.initState();  }  @override  Widget build(BuildContext context) {    return ChangeNotifierProvider(      create: (BuildContext context) => model,      child: Consumer(        builder: widget.builder,        child: widget.child,      ),    );  }}

最后

小编这里有一套关于Flutter的资料,可以帮助还不熟悉Flutter的你或者需要进阶的你。

a1e564ac27471992c3549c02844d31e1.png

当然还有Android学习PDF+架构视频+面试文档+源码笔记高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 这几块的内容

这些都是我现在闲暇还会反复翻阅的精品资料。里面对近几年的大厂面试高频知识点都有详细的讲解。相信可以有效的帮助大家掌握知识、理解原理。

分享给大家,非常适合近期有面试和想在技术道路上继续精进的朋友。也是希望可以帮助到大家提升进阶

如果你有需要的话,可以私信我【进阶】我发给你

喜欢本文的话,不妨顺手给我点个赞、评论区留言或者转发支持一下呗~

f89025f318e6322958f6269ae078f240.png
7919b07ec34509d4928419284cca8eb2.png
c9470b103d73a40b6a3d056c91c5dd32.png
ddb1030e71f13043c4853aedafc231d4.png
f344c1fcd1eded2cf7b5677706020456.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值