上一篇介紹了Provider
的基本原理與應用,今天我們來看看Provider.of
,以及Provider
的一些應用與類型吧
有的時候你不需要模型 ( 當初依要共享的狀態設計的 Model 類 ) 中的資料來改變UI,但是你可能還是需要使用該數據,比如,ClearCart
按鈕能夠清空購物車的所有商品,它不需要顯示購物車裡的內容,只需要調用clear()
方法
我們一樣可以使用Consumer<CartModel>
來實現這個效果,不過這麼實現有點浪費,因為我們這樣在清空購物車時會重構了一個無需重構的widget,此時我們可以使用Provider.of
,並且將listen
設置為false
,這樣就可以使用特定數據,又不會讓整體 UI 框架重構
例如:
Provider.of<CartModel>(context, listen: false).removeAll();
在build 方法中使用上面的代碼,當notifyListeners
被調用的時候,並不會使widget 被重構
Provider.of<T>(context)
和Consumer
常常會被拿來對比,二者到底有什麼差別呢,我們來看看Consumer
的原始碼
@override
Widget build(BuildContext context) {
return builder(
context,
Provider.of<T>(context),
child,
);
}
可以發現,Consumer
就是通過Provider.of<T>(context)
來實現的,但是它會將當數據發生變化後,把監聽者的widget 要重建的範圍限制地更小,而Provider.of<T>(context)
將會把調用了該方法的context 作為觀察者,並在notifyListeners
的時候通知其刷新 ( listen 預設都是true 除非去設定false ,不然一樣會去更新重建 )
結論:我們可以把Provider
的使用分為兩種,數據資料改變的觸發者以及監聽/觀察者
Provider.of<Counter>(context, listen: false)
來獲取資料Consumer
參考文件,當前provider
版本:provider 4.3.2
如果我們想讓某個物件資料( 變量 ) 能夠被一個widget 以及其子widget 引用,我們可以使用Provider
的建構create
來建立所要的物件widget
例如:
Provider(
create: (_) => MyModel(), //所要共享的物件資料
child: ...
)
如果要將隨時間變化的變量傳遞給對象,請考慮使用ProxyProvider:
int count;
ProxyProvider0(
update: (_, __) => MyModel(count),
child: ...
)
當使用
Provider
的create/update
回調時,需要注意的是,預設情況下create/update
是lazy
調用,也就是說,只有我們Provider
中的數據至少被請求一次後,create/update
才會被調用,如果我們想做一些預先處理,我們可以使用lazy
參數來禁止這一特性例如:
MyProvider( create: (_) => Something(), lazy: false, )
若想使用已建立的Provider 物件,則使用Provider
的建構函數.value
若不是用建構函數
.value
使用已存在的Provider 物件可能會在物件仍在使用時,就被調用其dispose
方法,使此Provider 物件被釋放資源 (dispose
方法 後面會再介紹 )
例如:
MyChangeNotifier variable;
ChangeNotifierProvider.value(
value: variable,
child: ...
)
最簡單的讀取資料的方式就是使用BuildContext
的擴展方法:
context.watch<T>()
:讓widget 去監聽共享資料 T 的變化context.read<T>()
:直接返回 T ,而不去監聽它的變化context.select<T, R>(R cb(T value))
:讓widget 可以只去監聽 T 的部分變化當然也可以使用我們之前講的靜態方法Provider.of<T>(context)
,它和watch/read
的行為很像,這也是在上面擴展方法出現之前,我們獲取數據的方式之一
這些方法將從傳遞過來的BuildContext
相關的widget 開始查詢它的widget tree,並返回找到的符合類型 T 的最近變量 ( 如果未找到則拋出異常 )
範例:
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
// 不要忘記將想要獲取的對像類型傳遞給`watch`
context.watch<String>(),
);
}
}
另外,也可以不使用這些方法,而可以使用Consumer
和Selector
,這些對於性能優化或在難以獲得Provider
的BuildContext 後代很有用 (Selector
會在後面介紹 )
typedef Disposer<T> = void Function(BuildContext context, T value);
Provider
提供了dispose
的回調,當Provider 所在節點被移除的時候,它就會啟動Disposer<T>
,然後我們便可以在這裡釋放資源
在之前使用過BLoC 的經驗,我們遇到過一個問題,BloC 使用了觀察者模式,它旨在替代StatefulWidget,然而大量的流使用完畢之後必須close 掉,以釋放資源,但是應該在什麼時候釋放資源呢?而StatelessWidget
並沒有類似於dispose
的方法,讓我們不得不為了釋放資源而使用StatefulWidget
,然而Provider
則為我們解決了這一點提供了dispose
的回調
例如:
我們有一個 BLoC
class ExampleBLoC {
StreamController<String> _data = StreamController<String>.broadcast();
get data => _data.stream;
doSomething(String text) {
// ...
}
dispose() {
_data.close();
}
}
我們想要提供這個BLoC卻又不想使用StatefulWidget
,此時可以透過Provider
使用
Provider(
create:(_) => ExampleBLoC(),
dispose:(_, ExampleBLoC bloc) => bloc.dispose(), //在dispose 回調中關閉不再使用的流,即解決了資源釋放的問題
)
相當於Consumer
,可以在特定值改變時,再去重新構建widget
例如,當List 長度改變時,才重新構建widget:
Selector<List, int>(
selector: (_, list) => list.length,
builder: (_, length, __) {
return Text('$length');
}
);
將多個Provider 合併成一個widget
Provider<Something>(
create: (_) => Something(),
child: Provider<SomethingElse>(
create: (_) => SomethingElse(),
child: Provider<AnotherThing>(
create: (_) => AnotherThing(),
child: someWidget,
),
),
),
變為:
MultiProvider(
providers: [
Provider<Something>(create: (_) => Something()),
Provider<SomethingElse>(create: (_) => SomethingElse()),
Provider<AnotherThing>(create: (_) => AnotherThing()),
],
child: someWidget,
)
將多個provider 個別共享的值,整合成一個新對象,並將結果發送給外層的 provider,當所依賴的多個provider 的任一個發生變化時,此新對象都會更新
範例:
ProxyProvider
来構建了一個依賴其他provider 提供的計數器的範例
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
ProxyProvider<Counter, Translations>(
update: (_, counter, __) => Translations(counter.value),
),
],
child: Foo(),
);
}
class Translations {
const Translations(this._value);
final int _value;
String get title => 'You clicked $_value times';
}
ProxyProvider vs ProxyProvider2 vs ProxyProvider3
,… 類別名後面的數字是ProxyProvider
依賴其他providers
的數量
剩下還有幾種 Provider,就不一一介紹了,這邊來列個大概的種類
Provider
:最基礎的provider
,會獲取一個值並expose出來共享
ListenableProvider
:用來共享可以監聽的對象,會隨著監聽對象的改變而更新widget
ChangeNotifierProvider
:ListenableProvider
和ChangeNotifierProvider
其實是 父與子的關係,ChangeNotifierProvider
在ListenableProvider
的基礎上,且可以在 Model 中覆寫 ChangeNotifier 的 dispose 方法,來釋放其資源,這對於複雜 Model 的情況下十分有用,在需要的時候能夠自動調用其 disposer 方法
ValueListenableProvider
:要求builder
返回的對象必須是ValueNotifier<T>
的子類,T
是需要共享的數據類型,用於處理只有一個單一變化數據的ChangeNotifier,通過ValueListenable 處理的類不再需要數據更新的時候調用notifyListeners
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)';
}
StreamProvider
:監聽一個流,並且expose 其最近發送的值
FutureProvider
:提供了一個 Future 給其子孫節點,並在 Future 完成時,通知依賴的子孫節點進行刷新
今天就介紹到這邊,Provider 的內容也告一段落,已經完成我們對Provider
的應用介紹,接下來我們將我們之前Android Studio 的登入範例,用Provider 改寫看看吧!