2022-02-24 Flutter Provider使用

https://zhuanlan.zhihu.com/p/392551608

在各种前端开发中,由于状态管理对于App的开发维护成本,性能等方面都起着至关重要的作用,所以选择合适的状态管理框架显得尤为重要。Flutter作为跨平台框架的后起之秀,背靠Google大树,短时间内开发者们在开源社区提供了多种状态管理框架。而Provider是官方推荐的状态管理方式之一,可用作跨组件的数据共享。本文对Provider框架的使用方法做了介绍,并在最后对主流的状态管理框架进行比较。

使用

Provider,通常使用ChangeNotifierProvider配合ChangeNotifier一起使用来实现状态的管理与Widget的更新。

其中 ChangeNotifier 是系统提供的,用来负责数据的变化通知。 ChangeNotifier 是一个类

ChangeNotifierProvider本质上其实就是Widget,它作为父节点Widget,可将数据共享给其所有子节点Widget使用或更新。

所以通常我们只需要三步即可利用Provider来实现状态管理。

1.创建混合或继承ChangeNotifier的Model,用来实现数据更新的通知并监听数据的变化。

2.创建ChangeNotifierProvider 或者用MultiProvider组合,用来声明Provider,实现跨组建的数据共享。

3.接收共享数据。

1、model部分

import 'package:flutter/material.dart';

class CountModel extends ChangeNotifier {
  int _count;

  CountModel() : _count = 1;

  int get count => _count;

  set count(int count) {
    _count = count;
    notifyListeners();
  }

  void add() {
    this.count = _count+1;
  }

  void minus() {
    this.count = _count-1;
  }
}

model部分的写法:

1、 类

class 普通类 (成员方法必须要实现)
mixin 多继承,混入类(不能有构造方法)
abstract class 抽象类 (定义抽象方法)

2、类继承, 3个关键字:

extends 继承
implements 继承(父类方法必须要实现)
with 多继承(父类不能有构造方法)

3、约束:

on 约束(继承mixin类的类,必须同时继承约束类)

调用notifyListeners方法通知widget刷新界面

2、 视图部分

接受共享数据

使用ChangeNotifierProvider

Container(
    width: double.maxFinite,
    child: ChangeNotifierProvider<CountModel>.value(
      value: this.countModel,
      builder: (context, child) {
        return Container(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              ChildAPage(),
              Text(
                  context.watch<CountModel>().count.toString()),
            ],
          ),
        );
      },
    ),
  )

NO.1:
ChangeNotifierProvider的 builder方法与child方法区别

ChangeNotifierProvider.value({
  Key key,
  @required T value,
  TransitionBuilder builder,
  Widget child,
}) 

builder: (context, child)
builder的类型是= Widget Function(BuildContext context, Widget child);
这里可以获取当前的context。

  @override
  Widget buildWithChild(BuildContext context, Widget child) {
    assert(
      builder != null || child != null,
      '$runtimeType used outside of MultiProvider must specify a child',
    );
    return _InheritedProviderScope<T>(
      owner: this,
      child: builder != null
          ? Builder(
              builder: (context) => builder(context, child),
            )
          : child,
    );
  }

NO.2:

ChangeNotifierProvider<CountModel>.value value方法和 create方法

ChangeNotifierProvider<CountModel>.value(
      value: this.countModel


ChangeNotifierProvider<CountModel>(
      create: (context) => CountModel(),

ChangeNotifierProvider 非常聪明,它 不会 重复实例化 CartModel,除非在个别场景下。如果该实例已经不会再被调用, ChangeNotifierProvider 也会自动调用 CartModel 的 dispose() 方法。

接受共享数据

调用方法获取model 变化的值。 这两种方法作用相同:

1、context.watch<CountModel>().count

2、Provider.of<CountModel>(context).count

3、 Consumer<CountModel>(
      builder: (context, model, child) {
         return Text(
           'Consumer: ${model.count.toString()}');
     },
    )


使用 Consumer:

Consumer widget 唯一必须的参数就是 builder。当 ChangeNotifier 发生变化的时候会调用 builder 这个函数。(换言之,当你在模型中调用 notifyListeners() 时,所有相关的 Consumer widget 的 builder 方法都会被调用。)

builder 在被调用的时候会用到三个参数。第一个是 context。在每个 build 方法中都能找到这个参数。

builder 函数的第二个参数是 ChangeNotifier 的实例。它是我们最开始就能得到的实例。你可以通过该实例定义 UI 的内容。

第三个参数是 child,用于优化目的。如果 Consumer 下面有一个庞大的子树,当模型发生改变的时候,该子树 并不会 改变,那么你就可以仅仅创建它一次,然后通过 builder 获得该实例。

3、修改model

在外层父widget可以调用:

countModel.minus();

在子widget可以调用:

 Provider.of<CountModel>(context, listen: false).add();

4、 使用MultiProvider

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_test/test/MultiProvider/CountA.dart';

class MultiProviderPage extends StatefulWidget {
  const MultiProviderPage({Key key}) : super(key: key);

  @override
  _MultiProviderState createState() => _MultiProviderState();
}

class _MultiProviderState extends State<MultiProviderPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('MultiProvider使用'),
        ),
        body: SafeArea(
          child: Container(
            margin: EdgeInsets.only(top: 20, left: 20, right: 20),
            child: MultiProvider(
              providers: [
                //Could not find the correct Provider<CountA> above this MultiProviderPage Widget
                ChangeNotifierProvider<CountA>(
                  create: (_) => CountA(),
                ),
                ChangeNotifierProvider<CountB>(
                  create: (_) => CountB(),
                ),
              ],
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  WidgetA(),
                ],
              ),
            ),
          ),
        ));
  }
}

class WidgetA extends StatelessWidget {
  const WidgetA({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Column(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text(
              Provider.of<CountA>(context).astr,
              style: TextStyle(color: Colors.black),
            ),
            Text(
              context.watch<CountA>().astr,
              style: TextStyle(color: Colors.black),
            ),
            Text(
              Provider.of<CountB>(context).astr,
              style: TextStyle(color: Colors.black),
            ),
            Text(
              context.watch<CountB>().astr,
              style: TextStyle(color: Colors.black),
            ),
          ],
        ),
        ButtonBar(
          alignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
            InkWell(
              onTap: () {
                DateTime now = new DateTime.now();
                Provider.of<CountA>(context, listen: false).astr = 'a:$now';
              },
              child: Text(
                '修改a',
              ),
            ),
            InkWell(
              onTap: () {
                DateTime now = new DateTime.now();
                Provider.of<CountB>(context, listen: false).astr = 'b:$now';
              },
              child: Text(
                '修改b',
              ),
            ),
          ],
        ),
      ],
    );
  }
}

原理

Provider实际上是个无状态的StatelessWidget,通过包装了InheritedWidget实现父子组件的数据共享,

通过自定义InheritedElement实现刷新。

Provider通过与ChangeNotifier配合使用,实现了观察者模式,Provider会将子组件添加到父组件的依赖关系中,

在notifyListeners()时,会执行InheritedContext.markNeedsNotifyDependents(),将组件标记为dirty等待重绘。

Consumer会只将被它包裹住的子组件注册给父的_dependents形成依赖关系,从而实现了局部更新。

demo

AddPage.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_test/test/ChildPage.dart';
import 'package:provider_test/test/model/count.dart';

class AddPage extends StatefulWidget {
  AddPage({this.countModel});

  CountModel countModel;

  @override
  _AddPageState createState() => _AddPageState()..countModel = countModel;
}

class _AddPageState extends State<AddPage> {
  CountModel countModel;

  @override
  void setState(fn) {
    // TODO: implement setState
    super.setState(fn);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
          color: Colors.white,
          child: Column(
            // mainAxisSize: MainAxisSize.max,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text(
                '我是父widget',
                style: TextStyle(
                  fontSize: 20,
                  color: Colors.black,
                ),
              ),
              Stack(
                alignment: Alignment.center,
                children: <Widget>[
                  Container(
                    width: double.maxFinite,
                    child: ChangeNotifierProvider<CountModel>.value(
                      value: this.countModel,
                      builder: (context, child) {
                        return Container(
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: <Widget>[
                              ChildAPage(),
                              Text(
                                  'countModel: ${countModel.count.toString()}'),
                              Text(
                                  'Provider.of: ${Provider.of<CountModel>(context).count.toString()}'),
                              Text(
                                  'context.watch: ${context.watch<CountModel>().count.toString()}'),
                              Consumer<CountModel>(
                                builder: (context, model, child) {
                                  return Text(
                                      'Consumer: ${model.count.toString()}');
                                },
                              ),
                            ],
                          ),
                        );
                      },
                    ),
                  ),
                  Positioned(
                    bottom: 20,
                    right: 20,
                    child: Container(
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: <Widget>[
                          FlatButton(
                              color: Colors.yellow,
                              onPressed: () {
                                countModel.add();
                              },
                              child: Icon(Icons.add)),
                          FlatButton(
                              color: Colors.red,
                              onPressed: () {
                                countModel.minus();
                              },
                              child: Icon(Icons.remove)),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}


ChildPage.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_test/test/model/count.dart';

class ChildAPage extends StatelessWidget {
  const ChildAPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.greenAccent,
      child: Column(
        children: <Widget>[
          Text(
            '我是子widget',
            style: TextStyle(
              fontSize: 20,
            ),
          ),
          Text('${Provider.of<CountModel>(context).count}'),
          Text('${context.watch<CountModel>().count}'),
          FlatButton.icon(
            color: Colors.red,
            icon: Icon(Icons.add_circle_outline),
            label: Text('加'),
            onPressed: () {
              //Provider.of<CountModel>(context).add();
              Provider.of<CountModel>(context, listen: false).add();
            },
          ),
          FlatButton.icon(
            icon: Icon(Icons.remove_circle_outline),
            color: Colors.yellow,
            label: Text('减'),
            onPressed: () {
              //Provider.of<CountModel>(context).minus();
              Provider.of<CountModel>(context, listen: false).minus();
            },
          )
        ],
      ),
    );
  }
}

CountModel.dart


import 'package:flutter/material.dart';

class CountModel extends ChangeNotifier {
  int _count;

  CountModel() : _count = 1;

  int get count => _count;

  set count(int count) {
    _count = count;
    notifyListeners();
  }

  void add() {
    this.count = _count+1;
  }

  void minus() {
    this.count = _count-1;
  }
}

几种状态同步框架的对比和选择

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

推荐阅读更多精彩内容