Flutter Provider状态管理---四种消费者使用分析

文章系列

Flutter Provider状态管理---介绍、类图分析、基本使用

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

Flutter Provider状态管理---四种消费者使用分析

Flutter Provider状态管理---MVVM架构实战

视频系列

Flutter Provider状态管理---介绍、类图分析、基本使用

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

Flutter Provider状态管理---四种消费者使用分析

Flutter Provider状态管理---MVVM架构实战

源码仓库地址

github仓库地址

前言

在上一篇文章中我们对Provider的8种提供者进行了详细的描述以及用对应的案例说明他们的区别,那么这一节我们来聊一聊Provider的消费者,如果去优化你的项目结构以及它们的使用区别。

Provider.of

Provider.of<T>(context)Provider为我们提供的静态方法,当我们使用该方法去获取值的时候会返回查找到的最近的T类型的provider给我们,而且也不会遍历整个组件树,下面我们看下代码:

第一步:定义模型

我们定义了一个CountNotifier1的模型,后面所有的示例代码将围绕该模型来演示

import 'package:flutter/material.dart';

class CountNotifier1 with ChangeNotifier {

  int count = 0;

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

第二步:应用程序入口设置

return ChangeNotifierProvider(
  create: (_) => CountNotifier1(),
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: ConsumerExample(),
  ),
);

第三步:使用Provider.of

这里读取值和点击按钮+1时都是通过Provider.of<T>()来获取及使用。

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/consumer_example/count_notifier1.dart';
import 'package:provider/provider.dart';

class ConsumerExample extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text("ConsumerExample"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(Provider.of<CountNotifier1>(context).count.toString(),
              style: TextStyle(
                  color: Colors.red,
                  fontSize: 50
              ),
            ),
            Padding(
              padding: EdgeInsets.only(
                top: 20
              ),
              child: ElevatedButton(
                onPressed: (){
                  Provider.of<CountNotifier1>(context).increment();
                },
                child: Text("点击加1"),
              ),
            )
          ],
        ),
      ),
    );
  }
}

错误日志

当我们运行代码的时候会提示一个报错,它提示说试图从Widget树外部监听提供者公开的值,如果要修复可以把listen改成false,这个问题其实是在Provider 4.0.2后会出现的,最主要的是它的默认行为就是ture,错误日志如下:

======== Exception caught by gesture ===============================================================
The following assertion was thrown while handling a gesture:
Tried to listen to a value exposed with provider, from outside of the widget tree.

This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.

To fix, write:
Provider.of<CountNotifier1>(context, listen: false);

It is unsupported because may pointlessly rebuild the widget associated to the
event handler, when the widget tree doesn't care about the value.

The context used was: ConsumerExample(dependencies: [_InheritedProviderScope<CountNotifier1?>])
'package:provider/src/provider.dart':
Failed assertion: line 276 pos 7: 'context.owner!.debugBuilding ||
          listen == false ||
          debugIsInInheritedProviderUpdate'

When the exception was thrown, this was the stack: 
........
====================================================================================================

设置listen为false

Provider.of<CountNotifier1>(context, listen: false).increment();

运行结果

image

Consumer

Consumber只是在Widget中调用了Prvoider.of,并将其构造实现委托给了构造器,比如我们常见的Builder,如果你的Widget依赖多个模型,那么它还提供了Consumer23456方便调用,我们接下来对上面的案例采用Consumer来修改

用Consumer包裹组件

里面有个builder构造器,当我们把body改成下面重新运行后可以发现和使用Provider.of的结果一样,但是这里不需要在像使用Provider.of那样每次使用都要写一大串的重复性代码。

里面有三个属性:

  • context: 当前的上下文
  • Provider.of<T>(context): 模型对象
  • child: 子组件(不需要刷新的部分)
body: Consumer(
  builder: (_, CountNotifier1 countNotifier1, child) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(countNotifier1.count.toString(),
               style: TextStyle(
                 color: Colors.red,
                 fontSize: 50
               ),
              ),
          Padding(
            padding: EdgeInsets.only(
              top: 20
            ),
            child: ElevatedButton(
              onPressed: (){
                countNotifier1.increment();
              },
              child: Text("点击加1"),
            ),
          )
        ],
      ),
    );
  },
),

优化Consumer

优化方式一:尽可能调整Consumer的位置

我们在上面的代码中发现Center以及Column组件也被Consumer包裹了进来,但是这两个组件是不需要更新状态的,而我们每次构建的Widget的时候,会重建整个body,所以我们优化一下代码结构,看起来就像下面这样:

body: Center(
  child: Consumer(
    builder: (_, CountNotifier1 countNotifier1, child) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              countNotifier1.count.toString(),
              style: TextStyle(color: Colors.red, fontSize: 50),
            ),
            Padding(
              padding: EdgeInsets.only(top: 20),
              child: ElevatedButton(
                onPressed: () {
                  countNotifier1.increment();
                },
                child: Text("点击加1"),
              ),
            ),
                Container(
              child: Column(
                children: [
                  Text("更多组件1"),
                  Text("更多组件2"),
                  Text("更多组件3"),
                  Text("更多组件4"),
                  Text("更多组件5"),
                  Text("更多组件6"),
                ],
              ),
            )
          ],
        ),
      );
    },
  )
)

优化方式二:不需要刷新但被Consumer包裹的组件用child

比如上面我们有更多组件1-6甚至数百个组件无需刷新状态,但由于你用Consumer包裹会导致全部刷新,那么明显会导致性能的下降,你可能会想到单独用多个Consumer包裹需要刷新的组件就解决了,但这不就是本末倒置了吗,本身Provider是解决代码的健壮重复的代码,所以这个时候我们可以采用Consumer为我们提供的child参数,如下:

body: Center(
  child: Consumer(
    builder: (_, CountNotifier1 countNotifier1, child) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              countNotifier1.count.toString(),
              style: TextStyle(color: Colors.red, fontSize: 50),
            ),
            Padding(
              padding: EdgeInsets.only(top: 20),
              child: ElevatedButton(
                onPressed: () {
                  countNotifier1.increment();
                },
                child: Text("点击加1"),
              ),
            ),
            child!
          ],
        ),
      );
    },
    child: Container(
      child: Column(
        children: [
          Text("更多组件1"),
          Text("更多组件2"),
          Text("更多组件3"),
          Text("更多组件4"),
          Text("更多组件5"),
          Text("更多组件6"),
        ],
      ),
    ),
  )
),

Selector

Selector类和Consumer类似,只是对build调用Widget方法时提供更精细的控制,简单点来说,Selector也是一个消费者,它允许你可以从模型中准备定义哪些属性。

我们来举个例子:

比如,用户模型中有50个属性,但是我只需要更新年龄,这样我希望不需要重建用户名电话号码等组件,那么Selector就是用于解决这个问题,我们看一下示例:

第一步:定义模型

import 'package:flutter/material.dart';

class UserModel6 with ChangeNotifier {

  String name = "Jimi";
  int age = 18;
  String phone = "18888888888";


  void increaseAge() {
    age++;
    notifyListeners();
  }
}

第二步:应用程序入口设置

return ChangeNotifierProvider(
  create: (_) => UserModel6(),
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    home: SelectorExample(),
  ),
);

第三步:使用Selector更精细的控制

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/selector_example/user_model6.dart';
import 'package:provider/provider.dart';

class SelectorExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("SelectorExample"),
      ),
      body: Center(
        child: Selector<UserModel6, int>(
          selector: (_, userModel6) => userModel6.age,
          builder: (_, age, child) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(age.toString(),
                    style: TextStyle(
                        color: Colors.red,
                        fontSize: 30
                    )
                ),
                child!
              ],
            );
          },
          child: Padding(
            padding: EdgeInsets.all(20),
            child: ElevatedButton(
              onPressed: (){
                Provider.of<UserModel6>(context, listen: false).increaseAge();
              },
              child: Text("改变年龄"),
            ),
          ),
        ),
      ),
    );
  }
}

运行结果

image

InheritedContext

InheritedContextProvider内置扩展了BuildContext,它不保存了组件在树中自己位置的引用,我们在上面的案例中见到Provider.of<CountNotifier1>(context,listen: false),其实这个of方法就是使用Flutter查找树并找到Provider子类型为CountNotifier1而已。

三大方式:

  • BuildContext.read: BuildContext.read<CountNotifier1>()可以替换掉Provider.of<CountNotifier1>(context,listen: false),它会找到CountNotifier1并返回它。
  • BuildContext.watch: BuildContext.watch<CountNotifier1>()可以替换掉Provider.of<CountNotifier1>(context,listen: false),看起来和read没有什么不同,但是使用watch你就不需要在使用Consumer
  • BuildContext.select: BuildContext.select<CountNotifier1>()可以替换掉Provider.of<CountNotifier1>(context,listen: false),看起来和watch也没有什么不同,但是使用select你就不需要在使用Selector

BuildContext.read

下面两种使用方式结果是一样的

使用Provider.of<T>()

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';


class InheritedContextExample extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedContextExample"),
      ),

      /// Provider.of 获取值
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(Provider.of<CountNotifier2>(context).count.toString(),
              style: TextStyle(
                  color: Colors.red,
                  fontSize: 50
              ),
            ),
            Padding(
              padding: EdgeInsets.only(top: 20),
              child: ElevatedButton(
                onPressed: () => Provider.of<CountNotifier2>(context, listen: false).increment(),
                child: Text("点击加1"),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

使用BuildContext.read

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';


class InheritedContextExample extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedContextExample"),
      ),
      /// read 获取值
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(context.read<CountNotifier2>().count.toString(),
              style: TextStyle(
                  color: Colors.red,
                  fontSize: 50
              ),
            ),
            Padding(
              padding: EdgeInsets.only(top: 20),
              child: ElevatedButton(
                onPressed: () => Provider.of<CountNotifier2>(context, listen: false).increment(),
                child: Text("点击加1"),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

BuildContext.watch

使用Consumer

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';

class InheritedContextExample extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedContextExample"),
      ),
      /// Consumer 获取值
      body: Center(
        child: Consumer<CountNotifier2>(
          builder: (_, countNotifier2, child) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(countNotifier2.count.toString(),
                  style: TextStyle(
                      color: Colors.red,
                      fontSize: 50
                  ),
                ),
                Padding(
                  padding: EdgeInsets.only(top: 20),
                  child: ElevatedButton(
                    onPressed: () => countNotifier2.increment(),
                    child: Text("点击加1"),
                  ),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

使用BuildContext.watch

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';


class InheritedContextExample extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    /// 重要
    final countNotifier2 = context.watch<CountNotifier2>();

    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedContextExample"),
      ),
      /// watch 
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(countNotifier2.count.toString(),
              style: TextStyle(
                  color: Colors.red,
                  fontSize: 50
              ),
            ),
            Padding(
              padding: EdgeInsets.only(top: 20),
              child: ElevatedButton(
                onPressed: () => countNotifier2.increment(),
                child: Text("点击加1"),
              ),
            ),
          ],
        ),
      ),

    );
  }
}

BuildContext.select

使用Selector

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';

class InheritedContextExample extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedContextExample"),
      ),
      /// Selector
      body: Center(
        child: Selector<CountNotifier2, int>(
          selector: (_, countNotifier2) => countNotifier2.count,
          builder: (_, count, child) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(count.toString(),
                  style: TextStyle(
                      color: Colors.red,
                      fontSize: 50
                  ),
                ),
                child!
              ],
            );
          },
          child: Padding(
            padding: EdgeInsets.only(top: 20),
            child: ElevatedButton(
              onPressed: () => Provider.of<CountNotifier2>(context, listen: false).increment(),
              child: Text("点击加1"),
            ),
          ),
        ),
      ),

    );
  }
}

使用BuildContext.select

import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';


class InheritedContextExample extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    
    /// 重要
    final count = context.select((CountNotifier2 countNotifier2) => countNotifier2.count);

    return Scaffold(
      appBar: AppBar(
        title: Text("InheritedContextExample"),
      ),
      /// select
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(count.toString(),
              style: TextStyle(
                  color: Colors.red,
                  fontSize: 50
              ),
            ),
            Padding(
              padding: EdgeInsets.only(top: 20),
              child: ElevatedButton(
                onPressed: () => Provider.of<CountNotifier2>(context, listen: false).increment(),
                child: Text("点击加1"),
              ),
            )
          ],
        ),
      ),
    );
  }
}

总结

Flutter为我们提供了多种读取值的方式,上面我们对消费者四大类的一个使用和分析对比,大家可根据自己的实际应用场景去使用对应的方式。

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

推荐阅读更多精彩内容