2022-02-24 Flutter Provider使用

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

在各種前端開發(fā)中,由于狀態(tài)管理對(duì)于App的開發(fā)維護(hù)成本敲才,性能等方面都起著至關(guān)重要的作用承粤,所以選擇合適的狀態(tài)管理框架顯得尤為重要。Flutter作為跨平臺(tái)框架的后起之秀扰法,背靠Google大樹,短時(shí)間內(nèi)開發(fā)者們?cè)陂_源社區(qū)提供了多種狀態(tài)管理框架毅厚。而Provider是官方推薦的狀態(tài)管理方式之一塞颁,可用作跨組件的數(shù)據(jù)共享。本文對(duì)Provider框架的使用方法做了介紹,并在最后對(duì)主流的狀態(tài)管理框架進(jìn)行比較祠锣。

使用

Provider酷窥,通常使用ChangeNotifierProvider配合ChangeNotifier一起使用來實(shí)現(xiàn)狀態(tài)的管理與Widget的更新。

其中 ChangeNotifier 是系統(tǒng)提供的伴网,用來負(fù)責(zé)數(shù)據(jù)的變化通知蓬推。 ChangeNotifier 是一個(gè)類

ChangeNotifierProvider本質(zhì)上其實(shí)就是Widget,它作為父節(jié)點(diǎn)Widget澡腾,可將數(shù)據(jù)共享給其所有子節(jié)點(diǎn)Widget使用或更新沸伏。

所以通常我們只需要三步即可利用Provider來實(shí)現(xiàn)狀態(tài)管理。

1.創(chuàng)建混合或繼承ChangeNotifier的Model动分,用來實(shí)現(xiàn)數(shù)據(jù)更新的通知并監(jiān)聽數(shù)據(jù)的變化毅糟。

2.創(chuàng)建ChangeNotifierProvider 或者用MultiProvider組合,用來聲明Provider澜公,實(shí)現(xiàn)跨組建的數(shù)據(jù)共享姆另。

3.接收共享數(shù)據(jù)。

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 普通類 (成員方法必須要實(shí)現(xiàn))
mixin 多繼承,混入類(不能有構(gòu)造方法)
abstract class 抽象類 (定義抽象方法)

2甚侣、類繼承明吩, 3個(gè)關(guān)鍵字:

extends 繼承
implements 繼承(父類方法必須要實(shí)現(xiàn))
with 多繼承(父類不能有構(gòu)造方法)

3、約束:

on 約束(繼承mixin類的類殷费,必須同時(shí)繼承約束類)

調(diào)用notifyListeners方法通知widget刷新界面

2贺喝、 視圖部分

接受共享數(shù)據(jù)

使用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方法區(qū)別

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

builder: (context, child)
builder的類型是= Widget Function(BuildContext context, Widget child);
這里可以獲取當(dāng)前的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 非常聰明宗兼,它 不會(huì) 重復(fù)實(shí)例化 CartModel躏鱼,除非在個(gè)別場(chǎng)景下。如果該實(shí)例已經(jīng)不會(huì)再被調(diào)用殷绍, ChangeNotifierProvider 也會(huì)自動(dòng)調(diào)用 CartModel 的 dispose() 方法染苛。

接受共享數(shù)據(jù)

調(diào)用方法獲取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 唯一必須的參數(shù)就是 builder登钥。當(dāng) ChangeNotifier 發(fā)生變化的時(shí)候會(huì)調(diào)用 builder 這個(gè)函數(shù)畔师。(換言之,當(dāng)你在模型中調(diào)用 notifyListeners() 時(shí)牧牢,所有相關(guān)的 Consumer widget 的 builder 方法都會(huì)被調(diào)用看锉。)

builder 在被調(diào)用的時(shí)候會(huì)用到三個(gè)參數(shù)姿锭。第一個(gè)是 context。在每個(gè) build 方法中都能找到這個(gè)參數(shù)伯铣。

builder 函數(shù)的第二個(gè)參數(shù)是 ChangeNotifier 的實(shí)例呻此。它是我們最開始就能得到的實(shí)例。你可以通過該實(shí)例定義 UI 的內(nèi)容腔寡。

第三個(gè)參數(shù)是 child焚鲜,用于優(yōu)化目的。如果 Consumer 下面有一個(gè)龐大的子樹放前,當(dāng)模型發(fā)生改變的時(shí)候忿磅,該子樹 并不會(huì) 改變,那么你就可以僅僅創(chuàng)建它一次凭语,然后通過 builder 獲得該實(shí)例葱她。

3、修改model

在外層父widget可以調(diào)用:

countModel.minus();

在子widget可以調(diào)用:

 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實(shí)際上是個(gè)無狀態(tài)的StatelessWidget,通過包裝了InheritedWidget實(shí)現(xiàn)父子組件的數(shù)據(jù)共享却舀,

通過自定義InheritedElement實(shí)現(xiàn)刷新虫几。

Provider通過與ChangeNotifier配合使用,實(shí)現(xiàn)了觀察者模式挽拔,Provider會(huì)將子組件添加到父組件的依賴關(guān)系中辆脸,

在notifyListeners()時(shí),會(huì)執(zhí)行InheritedContext.markNeedsNotifyDependents()螃诅,將組件標(biāo)記為dirty等待重繪啡氢。

Consumer會(huì)只將被它包裹住的子組件注冊(cè)給父的_dependents形成依賴關(guān)系,從而實(shí)現(xiàn)了局部更新术裸。

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;
  }
}

幾種狀態(tài)同步框架的對(duì)比和選擇

640.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市袭艺,隨后出現(xiàn)的幾起案子搀崭,更是在濱河造成了極大的恐慌,老刑警劉巖猾编,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瘤睹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡答倡,警方通過查閱死者的電腦和手機(jī)轰传,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瘪撇,“玉大人获茬,你說我怎么就攤上這事港庄。” “怎么了锦茁?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵攘轩,是天一觀的道長。 經(jīng)常有香客問我码俩,道長度帮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任稿存,我火速辦了婚禮笨篷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瓣履。我一直安慰自己率翅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布袖迎。 她就那樣靜靜地躺著冕臭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪燕锥。 梳的紋絲不亂的頭發(fā)上辜贵,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音归形,去河邊找鬼托慨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛暇榴,可吹牛的內(nèi)容都是我干的厚棵。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼蔼紧,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼婆硬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起奸例,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤柿祈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后哩至,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體躏嚎,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年菩貌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卢佣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡箭阶,死狀恐怖虚茶,靈堂內(nèi)的尸體忽然破棺而出戈鲁,到底是詐尸還是另有隱情,我是刑警寧澤嘹叫,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布婆殿,位于F島的核電站,受9級(jí)特大地震影響罩扇,放射性物質(zhì)發(fā)生泄漏婆芦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一喂饥、第九天 我趴在偏房一處隱蔽的房頂上張望消约。 院中可真熱鬧,春花似錦员帮、人聲如沸或粮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽氯材。三九已至,卻和暖如春硝岗,著一層夾襖步出監(jiān)牢的瞬間氢哮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工辈讶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留命浴,地道東北人娄猫。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓贱除,卻偏偏與公主長得像,于是被迫代替她去往敵國和親媳溺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子月幌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容