一晾蜘、前言
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,
})
-
builder
:T Function(BuildContext context)
缀辩,返回要共享的數(shù)據(jù)Model
臭埋。 -
dispose
:void 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)BLoc
中sink
的獲取讶请,以及最后資源的釋放。
- 首先我們定義一個簡單的
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,
})
-
builder
:T 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
方法鳖链。
- 在改變數(shù)據(jù)后怎爵,調(diào)用
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,
})
-
builder
:T Function(BuildContext context)
,返回要共享的數(shù)據(jù)Model
芙委。 -
dispose
:void 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);
}
從源碼上可以看出灌侣,ListenableProvider
和ChangeNotifierProvider
其實是 父與子的關(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,
})
-
builder
:T Function(BuildContext context)
畏腕,返回要共享的數(shù)據(jù)Model
缴川。 -
dispose
:void 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
的使用方式:還是ChangeNotifierProvider
和StreamProvider
比較符合我們平時的使用場景。
而其它三種:
-
Provider
:不能監(jiān)聽數(shù)據(jù)改變煤傍,直接淘汰盖文。 -
ListenableProvider
:ChangeNotifierProvider
的原始版本。 -
ValueListenableProvider
:ChangeNotifierProvider
的增強版本蚯姆,但是怎么感覺用起來還更麻煩了五续。