Flutter 知識梳理 (狀態(tài)管理) - Provider 之各種 XXProvider 的使用姿勢

一晾蜘、前言

Provider是目前Google推薦的狀態(tài)管理方式之一辑畦,建議大家可以先看一下 Provider 的 Github 地址 了解基本的用法绊汹。

網(wǎng)上大多數(shù)介紹Provider的文章講的都是ChangeNotifierProvider搂捧,看完之后確實知道它是干什么的,以及怎么用滥沫。

然而其實還有其它的Provider供我們使用侣集,那么它們之間的區(qū)別和聯(lián)系是什么呢,官方文檔對它們的使用也沒有詳細的Demo兰绣,這篇文章就來總結(jié)一下它們的用法和區(qū)別世分。

Provider的分類有如下幾種:

  • Provider
  • ListenableProvider
  • ChangeNotifierProvider
  • ValueListenableProvider
  • StreamProvider

二、Provider

2.1 構(gòu)造函數(shù)

Provider的構(gòu)造函數(shù)如下:

  Provider({
    Key key,
    @required ValueBuilder<T> builder,
    Disposer<T> dispose,
    Widget child,
  })
  • builderT Function(BuildContext context)缀辩,返回要共享的數(shù)據(jù)Model臭埋。
  • disposevoid Function(BuildContext context, T value),在回調(diào)中釋放資源臀玄。

2.2 優(yōu)點 & 缺點

Provider的優(yōu)點:

  • 數(shù)據(jù)共享:通過Provider.of<T>(context)方法瓢阴,可以在以Provider為根節(jié)點的子樹中獲取到T的對象。
  • 通過dispose參數(shù)健无,可以進行資源的釋放荣恐。

但是Provider也有一個明顯的缺點:

  • 在共享數(shù)據(jù)發(fā)生改變時,不能通知它的監(jiān)聽者睬涧。

2.3 計數(shù)器例子

下面我們用一個經(jīng)典的計數(shù)器Demo演示一下Provider的使用募胃,為了方便對比,后面在介紹其它Provider時畦浓,也使用該例子。

根據(jù)Provider的兩個特點检疫,我們可以用它來實現(xiàn)BLocsink的獲取讶请,以及最后資源的釋放。

  • 首先我們定義一個簡單的Bloc模型屎媳。
import 'dart:async';

class ProviderBloc {

  int _count = 0;
  var _countController = StreamController<int>.broadcast();

  Stream<int> get stream => _countController.stream;
  int get count => _count;

  increment() {
    _countController.sink.add(++_count);
  }

  dispose() {
    _countController.close();
  }
}
  • 接下來編寫主界面夺溢。
    • 通過Provider.of<ProviderBloc>(context)我們可以在任意的地方獲取到ProviderBloc對象,獲得sink來更改數(shù)據(jù)烛谊。
    • 使用StreamBuilder監(jiān)聽Stream中數(shù)據(jù)的變化风响,刷新界面。
    • 在原始的Bloc模型上丹禀,頂層的Provider提供了dispose回調(diào)状勤,用于資源的釋放鞋怀。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'provider_bloc.dart';

void main() => runApp(_ProviderApp());

class _ProviderApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Provider<ProviderBloc>(
      builder: (context) => ProviderBloc(),
      dispose: (context, bloc) => bloc.dispose(),
      child: MaterialApp(
        home: Scaffold(
          appBar: AppBar(title: Text('Provider Demo')),
          body: CounterLabel(),
          floatingActionButton: CounterButton(),
        ),
      ),
    );
  }
}

class CounterButton extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: Provider.of<ProviderBloc>(context).increment,
      child: const Icon(Icons.add),
    );
  }

}

class CounterLabel extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: StreamBuilder<int>(
        builder: (context, snapshot) {
          return Text('you have push ${snapshot.data} times');
        },
        initialData: 0,
        stream: Provider.of<ProviderBloc>(context).stream,
      ),
    );
  }
}

三、ChangeNotifierProvider

ChangeNotifierProvider應該是大家見的最多的持搜,大多數(shù)介紹Provider的文章都是以它為例子密似,和 Provider 相比,它最大的優(yōu)點就是解決了數(shù)據(jù)改變后無法監(jiān)聽的問題葫盼。

3.1 構(gòu)造函數(shù)

ChangeNotifierProvider的構(gòu)造函數(shù)為如下:

  ChangeNotifierProvider({
    Key key,
    @required ValueBuilder<T> builder,
    Widget child,
  })
  • builderT Function(BuildContext context)残腌,返回要共享的數(shù)據(jù)Model

3.2 ChangeNotifier

使用ChangeNotifierProvider時贫导,它要求builder返回的數(shù)據(jù)Model必須是ChangeNotifier的子類抛猫。

  • 在改變數(shù)據(jù)后,調(diào)用notifyListener()方法孩灯。
  • 通過重寫它的dispose方法邑滨,可以完成和Provider一樣的資源釋放工作。
class Counter with ChangeNotifier {

  int _count = 0;
  int get count => _count;

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

  @override
  void dispose() {
    super.dispose();
  }
}

3.3 Provider.of<T>(context) & Consumer

在介紹Provider的文章中钱反,Provider.of<T>(context)Consumer都會被拿來對比掖看,一般都會推薦使用Consumer因為它會將數(shù)據(jù)發(fā)生變化后面哥,把監(jiān)聽者的 Widget 重建的范圍限制地更小哎壳。

項目中Provider的使用,可以分為兩個角色尚卫,數(shù)據(jù)改變的 觸發(fā)者監(jiān)聽者

  • 觸發(fā)者:如果只是需要獲取到數(shù)據(jù)model归榕,不需要監(jiān)聽變化(例如點擊按鈕),推薦使用Provider.of<Counter>(context, listen: false)來獲取數(shù)據(jù)model吱涉。
  • 監(jiān)聽者:推薦使用Consumer刹泄。

3.4 例子

3.4.1 定義數(shù)據(jù)模型

  • 首先,定義數(shù)據(jù)模型:
    • 在改變數(shù)據(jù)后怎爵,調(diào)用notifyListeners方法特石。
    • 如果需要釋放資源,那么需要重寫dispose方法鳖链。
import 'package:flutter/foundation.dart';

class Counter with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
  
  @override
  void dispose() {
    super.dispose();
  }
}

3.4.2 主文件

import 'counter_model.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(_ProviderApp());

class _ProviderApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Counter>(
      builder: (context) => Counter(),
      child: MaterialApp(
        home: Scaffold(
          appBar: AppBar(title: Text('Provider Demo')),
          body: CounterLabel(),
          floatingActionButton: CounterButton(),
        ),
      )
    );
  }
}

class CounterButton extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: Provider.of<Counter>(context, listen : false).increment,
      child: const Icon(Icons.add),
    );
  }

}

class CounterLabel extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Consumer<Counter>(
          builder: (BuildContext context, Counter counter, Widget child) {
            return Text('you have push ${counter.count} times');
          }),
    );
  }
}

四姆蘸、ListenableProvider

4.1 構(gòu)造函數(shù)

ListenableProvider的構(gòu)造函數(shù)為:

  ListenableProvider({
    Key key,
    @required ValueBuilder<T> builder,
    Disposer<T> dispose,
    Widget child,
  })
  • builderT Function(BuildContext context),返回要共享的數(shù)據(jù)Model芙委。
  • disposevoid Function(BuildContext context, T value)逞敷,在回調(diào)中釋放資源。

4.2 和 ChangeNotifierProvider 對比

先來一下ChangeNotifierProvider的源碼:

class ChangeNotifierProvider<T extends ChangeNotifier>
    extends ListenableProvider<T> implements SingleChildCloneableWidget {
  
  static void _disposer(BuildContext context, ChangeNotifier notifier) =>
      notifier?.dispose();

  ChangeNotifierProvider({
    Key key,
    @required ValueBuilder<T> builder,
    Widget child,
  }) : super(key: key, builder: builder, dispose: _disposer, child: child);

}

從源碼上可以看出灌侣,ListenableProviderChangeNotifierProvider其實是 父與子的關(guān)系推捐,ChangeNotifierProvider在它的基礎(chǔ)上:

  • 限制數(shù)據(jù)model的上限,要求必須是ChangeNotifier的子類侧啼。
  • 通過重寫ChangeNotifier.dispose()來完成資源的釋放牛柒,不需要傳入dispose參數(shù)給ChangeNotifierProvider堪簿。

4.3 例子

使用ListenableProvider時,假如我們沒有將數(shù)據(jù)模型定義成ChangeNotifier的子類焰络,那么需要自己進行監(jiān)聽者的管理戴甩,為了方便,我們還是繼續(xù)使用ChangeNotifier闪彼,其它地方的使用和ChangeNotifierProvider都是一樣的甜孤。

import 'counter_model.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(_ProviderApp());

class _ProviderApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return ListenableProvider<Counter>(
        builder: (context) => Counter(),
        dispose: (context, counter) => counter.dispose(),
        child: MaterialApp(
          home: Scaffold(
            appBar: AppBar(title: Text('Provider Demo')),
            body: CounterLabel(),
            floatingActionButton: CounterButton(),
          ),
        )
    );
  }
}

class CounterButton extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: Provider.of<Counter>(context, listen: false).increment,
      child: const Icon(Icons.add),
    );
  }

}

class CounterLabel extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Consumer<Counter>(
          builder: (BuildContext context, Counter counter, Widget child) {
            return Text('you have push ${counter.count} times');
          }),
    );
  }
}

五、ValueListenableProvider

5.1 構(gòu)造函數(shù)

  ValueListenableProvider({
    Key key,
    @required ValueBuilder<ValueNotifier<T>> builder,
    UpdateShouldNotify<T> updateShouldNotify,
    Widget child,
  })
  • builderT Function(BuildContext context)畏腕,返回要共享的數(shù)據(jù)Model缴川。
  • disposevoid Function(BuildContext context, T value),在回調(diào)中釋放資源描馅。

5.2 ValueNotifier

ValueListenableProvider要求builder返回的對象必須是ValueNotifier<T>的子類把夸,T是需要共享的數(shù)據(jù)類型。

ValueNotifier的定義如下:

class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {

  ValueNotifier(this._value);

  @override
  T get value => _value;
  T _value;
  set value(T newValue) {
    if (_value == newValue)
      return;
    _value = newValue;
    notifyListeners();
  }

  @override
  String toString() => '${describeIdentity(this)}($value)';
}

ValueNotifier是我們前面多次提到的ChangeNotifier的子類铭污,在改變_value時恋日,自動調(diào)用了notifyListeners方法,那么就參照之前的方法來使用它嘹狞。

5.3 例子

5.3.1 定義 ValueNotifier 的子類

import 'package:flutter/foundation.dart';

class CounterModel {

  int count;
  CounterNotifier wrapper;

  CounterModel(this.count);

}

class CounterNotifier extends ValueNotifier<CounterModel> {

  CounterNotifier(CounterModel value) : super(value) {
    value.wrapper = this;
  }

  @override
  void dispose() {
    super.dispose();
  }

}

5.3.2 主文件

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_notifier.dart';

void main() => runApp(_ProviderApp());

class _ProviderApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return ValueListenableProvider<CounterModel>(
        builder: (context) => CounterNotifier(CounterModel(0)),
        updateShouldNotify: (model1, model2) {
          print('updateShouldNotify');
          return model1.count != model2.count;
        },
        child: MaterialApp(
          home: Scaffold(
            appBar: AppBar(title: Text('Provider Demo')),
            body: _CounterLabel(),
            floatingActionButton: _CounterButton(),
          ),
        )
    );
  }
}

class _CounterButton extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () {
        CounterModel oldModel = Provider.of<CounterModel>(context, listen: false);
        CounterModel newModel = CounterModel(oldModel.count + 1);
        newModel.notifier = oldModel.notifier;
        oldModel.notifier.value = newModel;
        return;
      },
      child: const Icon(Icons.add),
    );
  }

}

class _CounterLabel extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Consumer<CounterModel>(
          builder: (BuildContext context, CounterModel model, Widget child) {
            return Text('you have push ${model.count} times');
          }),
    );
  }
}

5.4 疑問

這里有一個問題困擾了我很久:就是使用ValueNotifier<T>時必須要改變_value才會觸發(fā)notifyListeners()方法岂膳,而通過Provider.of<T>(context, listen: false)拿到的對象是_value,因此還需要在它里面保存ValueNotifier<T>的引用(或者將ValueNotifier定義成單例的模式)磅网,再設置一次達到觸發(fā)的效果谈截,感覺用起來很奇怪,不知道是不是我的使用方式有問題涧偷,網(wǎng)上也沒有找到相關(guān)的例子簸喂。

六、StreamProvider

StreamProvider用來結(jié)合Bloc使用燎潮。

6.1 構(gòu)造函數(shù)

6.1.1 使用 Stream 構(gòu)造

  StreamProvider({
    Key key,
    @required ValueBuilder<Stream<T>> builder,
    T initialData,
    ErrorBuilder<T> catchError,
    UpdateShouldNotify<T> updateShouldNotify,
    Widget child,
  })
  • builder:返回Bloc中的Stream喻鳄。
  • initialData:初始數(shù)據(jù)。
  • catchError:發(fā)生錯誤時候的回調(diào)跟啤。

6.1.2 使用 StreamController 構(gòu)造

  StreamProvider.controller({
    Key key,
    @required ValueBuilder<StreamController<T>> builder,
    T initialData,
    ErrorBuilder<T> catchError,
    UpdateShouldNotify<T> updateShouldNotify,
    Widget child,
  })
  • builder:返回Bloc中的StreamController诽表。

6.2 例子

6.2.1 定義單例模式

import 'dart:async';

class ProviderBloc {

  static ProviderBloc _instance;

  ProviderBloc._internal() {
    print("_internal");
  }

  static ProviderBloc _getInstance() {
    if (_instance == null) {
      _instance = ProviderBloc._internal();
    }
    return _instance;
  }

  factory ProviderBloc() => _getInstance();
  static ProviderBloc get instance => _getInstance();

  int _count = 0;
  var _countController = StreamController<int>.broadcast();

  Stream<int> get stream => _countController.stream;
  int get count => _count;

  increment() {
    _countController.sink.add(++_count);
  }

  dispose() {
    _countController.close();
  }

}

6.2.2 主文件

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'provider_bloc.dart';

void main() => runApp(_ProviderApp());

class _ProviderApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return StreamProvider<int> (
        builder: (context) {
          return ProviderBloc().stream;
        },
        catchError: (BuildContext context, Object error) {},
        initialData: 0,
        child: MaterialApp(
          home: Scaffold(
            appBar: AppBar(title: Text('Provider Demo')),
            body: CounterLabel(),
            floatingActionButton: CounterButton(),
          ),
        )
    );
  }
}

class CounterButton extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: ProviderBloc().increment,
      child: const Icon(Icons.add),
    );
  }

}

class CounterLabel extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Consumer<int>(
          builder: (BuildContext context, int value, Widget child) {
            return Text('you have push $value times');
          }),
    );
  }
}

七、FutureProvider

7.1 構(gòu)造函數(shù)

  FutureProvider({
    Key key,
    @required ValueBuilder<Future<T>> builder,
    T initialData,
    ErrorBuilder<T> catchError,
    UpdateShouldNotify<T> updateShouldNotify,
    Widget child,
  })
  • builder:返回一個Future<T>對象隅肥,異步任務的返回結(jié)果,在結(jié)果返回后袄简,會觸發(fā)Consumer的重建腥放。
  • initialData:初始數(shù)據(jù)。
  • catchError:發(fā)生錯誤的回調(diào)绿语。

7.2 和 FutureBuilder 的區(qū)別

FutureProvider和我們之前討論的Provider場景不太一樣秃症,它和FutureBuilder比較類似候址,就是在數(shù)據(jù)返回之前加載一個組件,等待數(shù)據(jù)返回值后种柑,重繪返回另一個組件岗仑。

它和FutureBuilder的區(qū)別在于:

  • FutureBuilder的數(shù)據(jù)請求和展示都是在一個組件當中,而FutureProvider是數(shù)據(jù)的請求者聚请,Consumer是展示者荠雕。
  • FutureBuilder的請求者和展示者是一對一的關(guān)系,而FutureProvider可以是一對多的關(guān)系驶赏。

7.3 例子

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(_ProviderApp());

class _ProviderApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return FutureProvider<int>(
        builder: (context) => _request(),
        initialData: 0,
        child: MaterialApp(
          home: Scaffold(
            appBar: AppBar(title: Text('Provider Demo')),
            body: CounterLabel(),
          ),
        )
    );
  }

  Future<int> _request() async {
    return await Future<int>.delayed(Duration(milliseconds: 3000)).then((int value) {
        return 300;
    });
  }
}

class CounterLabel extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(children: <Widget>[
        Consumer<int>(
            builder: (BuildContext context, int count, Widget child) {
              return Text('Observer1=$count');
            }),
        Consumer<int>(
            builder: (BuildContext context, int count, Widget child) {
              return Text('Observer2=$count');
            }),
      ],)
    );
  }
}

七炸卑、小結(jié)

對比以上幾種Provider的使用方式:還是ChangeNotifierProviderStreamProvider比較符合我們平時的使用場景。

而其它三種:

  • Provider:不能監(jiān)聽數(shù)據(jù)改變煤傍,直接淘汰盖文。
  • ListenableProviderChangeNotifierProvider的原始版本。
  • ValueListenableProviderChangeNotifierProvider的增強版本蚯姆,但是怎么感覺用起來還更麻煩了五续。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市龄恋,隨后出現(xiàn)的幾起案子疙驾,更是在濱河造成了極大的恐慌,老刑警劉巖篙挽,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荆萤,死亡現(xiàn)場離奇詭異,居然都是意外死亡铣卡,警方通過查閱死者的電腦和手機链韭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來煮落,“玉大人敞峭,你說我怎么就攤上這事〔醭穑” “怎么了旋讹?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長轿衔。 經(jīng)常有香客問我沉迹,道長,這世上最難降的妖魔是什么害驹? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任鞭呕,我火速辦了婚禮,結(jié)果婚禮上宛官,老公的妹妹穿的比我還像新娘葫松。我一直安慰自己瓦糕,他們只是感情好,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布腋么。 她就那樣靜靜地躺著咕娄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪珊擂。 梳的紋絲不亂的頭發(fā)上圣勒,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音未玻,去河邊找鬼灾而。 笑死,一個胖子當著我的面吹牛扳剿,可吹牛的內(nèi)容都是我干的旁趟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼庇绽,長吁一口氣:“原來是場噩夢啊……” “哼锡搜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瞧掺,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤耕餐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后辟狈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肠缔,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年哼转,在試婚紗的時候發(fā)現(xiàn)自己被綠了明未。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡壹蔓,死狀恐怖趟妥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情佣蓉,我是刑警寧澤披摄,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站勇凭,受9級特大地震影響疚膊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜虾标,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一酿联、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧夺巩,春花似錦贞让、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至美澳,卻和暖如春销部,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背制跟。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工舅桩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雨膨。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓擂涛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親聊记。 傳聞我的和親對象是個殘疾皇子撒妈,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355