-
新的 create/update 回調函數(shù)是懶加載的枷踏, 也就是說他們在對應的值第一次被讀取時才被調用, 而非provider首次被創(chuàng)建時.
如果你不需要這個特性始腾, 你可以通過將provider的lazy屬性置為false州刽, 來禁用懶加載
FutureProvider( create: (_) async => doSomeHttpRequest()空执, lazy: false浪箭, child: ... )
ProviderNotFoundError
更名為ProviderNotFoundException
.-
SingleChildCloneableWidget
接口被移除, 并被全新類型的組件SingleChildWidget
所替代參考這個 issue 來獲取遷移細節(jié).
-
Selector 現(xiàn)在會將先后的集合類型的值進行深層對比
如果你不需要這個特性辨绊, 你可以通過
shouldRebuild
參數(shù)來使其還原至舊有表現(xiàn).Selector<Selected奶栖, Consumed>( shouldRebuild: (previous, next) => previous == next门坷, builder: ...宣鄙, )
DelegateWidget
及其家族widget被移除, 現(xiàn)在想要自定義provider默蚌, 直接繼承 InheritedProvider 或當前存在的provider.
使用
暴露一個值
暴露一個新的對象實例
Providers不僅允許暴露出一個值冻晤,也可以創(chuàng)建/監(jiān)聽/銷毀它。
要暴露一個新創(chuàng)建的對象绸吸, 使用一個provider的默認構造函數(shù). 如果你想創(chuàng)建一個對象鼻弧, 不要使用 .value
構造函數(shù)设江, 否則可能會有你預期外的副作用。
查看該 StackOverflow Answer攘轩,來了解更多為什么不要使用.value
構造函數(shù)創(chuàng)建值叉存。
-
在create內創(chuàng)建新對象
Provider( create: (_) => MyModel(), child: ... )
-
不要使用
Provider.value
創(chuàng)建對象ChangeNotifierProvider.value( value: MyModel()度帮, child: ... )
-
不要以可能隨時間改變的變量創(chuàng)建對象
在這種情況下歼捏,如果變量發(fā)生變化,你的對象將永遠不會被更新
int count; Provider( create: (_) => MyModel(count)笨篷, child: ... )
如果你想將隨時間改變的變量傳入給對象瞳秽,請使用
ProxyProvider
:int count; ProxyProvider0( update: (_, __) => MyModel(count)率翅, child: ... )
注意:
在使用一個provider的create
/update
回調時寂诱,請注意回調函數(shù)默認是懶調用的。
也就是說安聘, 除非這個值被讀取了至少一次痰洒, 否則create
/update
函數(shù)不會被調用。
如果你想預先計算一些邏輯浴韭, 可以通過使用lazy
參數(shù)來禁用這一行為丘喻。
MyProvider(
create: (_) => Something(),
lazy: false念颈,
)
復用一個已存在的對象實例:
如果你已經(jīng)擁有一個對象實例并且想暴露出它泉粉,你應當使用一個provider的.value
構造函數(shù)。
如果你沒有這么做榴芳,那么在你調用對象的 dispose
方法時嗡靡, 這個對象可能仍然在被使用。
-
使用
ChangeNotifierProvider.value
來提供一個當前已存在的 ChangeNotifierMyChangeNotifier variable; ChangeNotifierProvider.value( value: variable窟感, child: ... )
-
不要使用默認的構造函數(shù)來嘗試復用一個已存在的 ChangeNotifier
MyChangeNotifier variable; ChangeNotifierProvider( create: (_) => variable讨彼, child: ... )
讀取一個值
讀取一個值最簡單的方式就是使用BuildContext
上的擴展屬性(由provider
注入)。
-
context.watch<T>()
柿祈, 一方法使得widget能夠監(jiān)聽泛型T
上發(fā)生的改變哈误。 -
context.read<T>()
,直接返回T
躏嚎,不會監(jiān)聽改變蜜自。 -
context.select<T, R>(R cb(T value))
卢佣,允許widget只監(jiān)聽T
上的一部分(R
)重荠。
或者使用 Provider.of<T>(context)
這一靜態(tài)方法,它的表現(xiàn)類似 watch
虚茶,而在你為 listen
參數(shù)傳入 false
時(如 Provider.of<T>(context戈鲁,listen: false)
)尾膊,它的表現(xiàn)類似于 read
。
值得注意的是荞彼,context.read<T>()
方法不會在值變化時使得widget重新構建冈敛, 并且不能在 StatelessWidget.build
/State.build
內調用. 換句話說, 它可以在除了這兩個方法以外的任意之處調用鸣皂。
上面列舉的這些方法會與傳入的 BuildContext
關聯(lián)的widget開始查找widget樹抓谴,并返回查找到的最近的類型T的變量(如果沒有找到, 將拋出錯誤)寞缝。
值得注意是這一操作的復雜度是 O(1)癌压,它實際上并不涉及遍歷整個組件樹。
結合上面第一個向外暴露一個值的例子荆陆,這個widget會讀取暴露出的String
并渲染Hello World
滩届。
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
// Don't forget to pass the type of the object you want to obtain to `watch`!
context.watch<String>(),
);
}
}
或者不使用這些方法被啼,我們也可以使用 Consumer 與 Selector帜消。
這些往往在性能優(yōu)化以及當很難獲取到provider的構建上下文后代(difficult to obtain a BuildContext
descendant of the provider) 時是很有用的。
參見 FAQ 或關于Consumer 和 Selector 的文檔部分了解更多.
MultiProvider
當在大型應用中注入較多狀態(tài)時浓体, Provider
很容易變得高度耦合:
Provider<Something>(
create: (_) => Something()泡挺,
child: Provider<SomethingElse>(
create: (_) => SomethingElse(),
child: Provider<AnotherThing>(
create: (_) => AnotherThing()命浴,
child: someWidget娄猫,
),
)生闲,
)媳溺,
使用MultiProvider
:
MultiProvider(
providers: [
Provider<Something>(create: (_) => Something()),
Provider<SomethingElse>(create: (_) => SomethingElse())碍讯,
Provider<AnotherThing>(create: (_) => AnotherThing())悬蔽,
],
child: someWidget冲茸,
)
以上兩個例子的實際表現(xiàn)是一致的屯阀, MultiProvider
唯一改變的就是代碼書寫方式.
ProxyProvider
從3.0.0開始缅帘, 我們提供了一種新的provider: ProxyProvider
.
ProxyProvider
能夠將多個來自于其他的providers的值聚合為一個新對象轴术,并且將結果傳遞給Provider
。
這個新對象會在其依賴的任一providers更新后被更新
下面的例子使用ProxyProvider
钦无,基于來自于另一個provider的counter值進行轉化逗栽。
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
vsProxyProvider2
vsProxyProvider3
摧冀, ...類名后的數(shù)字是
ProxyProvider
依賴的其他providers的數(shù)量 -
ProxyProvider
vsChangeNotifierProxyProvider
vsListenableProxyProvider
倍踪, ...它們工作的方式是相似的, 但
ChangeNotifierProxyProvider
會將它的值傳遞給ChangeNotifierProvider
而非Provider
索昂。
FAQ
我是否能查看(inspect)我的對象的內容?
Flutter提供的開發(fā)者工具能夠展示特定時刻下的widget樹建车。
既然providers同樣是widget,他們同樣能通過開發(fā)者工具進行查看椒惨。
點擊一個provider缤至, 即可查看它暴露出的值:
[圖片上傳失敗...(image-6c27fc-1623978187784)]
以上的開發(fā)者工具截圖來自于 /example
文件夾下的示例
開發(fā)者工具只顯示"Instance of MyClass", 我能做什么?
默認情況下康谆, 開發(fā)者工具基于toString
领斥,也就使得默認結果是 "Instance of MyClass"。
如果要得到更多信息沃暗,你有兩種方式:
-
使用Flutter提供的 Diagnosticable API
在大多數(shù)情況下戒突, 只需要在你的對象上使用 DiagnosticableTreeMixin 即可,以下是一個自定義 debugFillProperties 實現(xiàn)的例子:
class MyClass with DiagnosticableTreeMixin { MyClass({this.a描睦, this.b}); final int a; final String b; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); // list all the properties of your class here. // See the documentation of debugFillProperties for more information. properties.add(IntProperty('a'膊存, a)); properties.add(StringProperty('b', b)); } }
-
重寫
toString
方法如果你無法使用 DiagnosticableTreeMixin (比如你的類在一個不依賴于Flutter的包中)忱叭, 那么你可以通過簡單重寫
toString
方法來達成效果隔崎。這比使用 DiagnosticableTreeMixin 要更簡單,但能力也有著不足: 你無法 展開/折疊 來查看你的對象內部細節(jié)韵丑。
class MyClass with DiagnosticableTreeMixin { MyClass({this.a爵卒, this.b}); final int a; final String b; @override String toString() { return '$runtimeType(a: $a, b: $b)'; } }
在獲得initState
內部的Providers時發(fā)生了異常撵彻, 該做什么?
這個異常的出現(xiàn)是因為你在嘗試監(jiān)聽一個來自于永遠不會再次被調用的生命周期的provider钓株。
這意味著你要么使用另外一個生命周期(build
),要么顯式指定你并不在意后續(xù)更新陌僵。
也就是說轴合,不應該這么做:
initState() {
super.initState();
print(context.watch<Foo>().value);
}
你可以這么做:
Value value;
Widget build(BuildContext context) {
final value = context.watch<Foo>.value;
if (value != this.value) {
this.value = value;
print(value);
}
}
這會且只會在value
變化時打印它。
或者你也可以這么做:
initState() {
super.initState();
print(context.read<Foo>().value);
}
這樣只會打印一次value碗短,并且會忽視后續(xù)的更新
如何控制我的對象上的熱更新?
你可以使你提供的對象實現(xiàn) ReassembleHandler
類:
class Example extends ChangeNotifier implements ReassembleHandler {
@override
void reassemble() {
print('Did hot-reload');
}
}
通常會和 provider
一同使用:
ChangeNotifierProvider(create: (_) => Example())受葛,
使用ChangeNotifier時, 在更新后出現(xiàn)了異常, 發(fā)生了什么?
這通常是因為你在widget樹正在構建時总滩,從ChangeNotifier的某個后代更改了ChangeNotifier纲堵。
最典型的情況是在一個future被保存在notifier內部時發(fā)起http請求。
initState() {
super.initState();
context.read<MyNotifier>().fetchSomething();
}
這是不被允許的闰渔,因為更改會立即生效.
也就是說席函,一些widget可能在變更發(fā)生前構建,而有些則可能在變更后. 這可能造成UI不一致冈涧, 因此是被禁止的向挖。
所以,你應該在一個整個widget樹所受影響相同的位置執(zhí)行變更:
-
直接在你的model的 provider/constructor 的
create
方法內調用:class MyNotifier with ChangeNotifier { MyNotifier() { _fetchSomething(); } Future<void> _fetchSomething() async {} }
在不需要傳入形參的情況下炕舵,這是相當有用的何之。
-
在框架的末尾異步的執(zhí)行(
Future.microtask
):initState() { super.initState(); Future.microtask(() => context.read<MyNotifier>(context).fetchSomething(someValue); ); }
這可能不是理想的使用方式,但它允許你向變更傳遞參數(shù)咽筋。
我必須為復雜狀態(tài)使用 ChangeNotifier 嗎?
不溶推。
你可以使用任意對象來表示你的狀態(tài),舉例來說奸攻,一個可選的架構方案是使用Provider.value
配合StatefulWidget
這是一個使用這種架構的計數(shù)器示例:
class Example extends StatefulWidget {
const Example({Key key蒜危, this.child}) : super(key: key);
final Widget child;
@override
ExampleState createState() => ExampleState();
}
class ExampleState extends State<Example> {
int _count;
void increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Provider.value(
value: _count,
child: Provider.value(
value: this睹耐,
child: widget.child辐赞,
),
);
}
}
我們可以通過這樣來讀取狀態(tài):
return Text(context.watch<int>().toString());
并且這樣來修改狀態(tài):
return FloatingActionButton(
onPressed: () => context.read<ExampleState>().increment()硝训,
child: Icon(Icons.plus_one)响委,
);
或者你還可以自定義provider.
我可以創(chuàng)建自己的Provider嗎?
可以,provider
暴露出了所有構建功能完備的provider所需的組件窖梁,它包含:
-
SingleChildStatelessWidget
赘风, 使任意widget能夠與MultiProvider
協(xié)作, 這個接口被暴露為包package:provider/**single_child_widget
的一部分** -
InheritedProvider纵刘,在使用
context.watch
時可獲取的通用InheritedWidget
邀窃。
這里有個使用 ValueNotifier
作為狀態(tài)的自定義provider例子:
https://gist.github.com/rrousselGit/4910f3125e41600df3c2577e26967c91
我的widget重構建太頻繁了, 我能做什么?
你可以使用 context.select
而非 context.watch
來指定只監(jiān)聽對象的部分屬性:
舉例來說假哎,你可以這么寫:
Widget build(BuildContext context) {
final person = context.watch<Person>();
return Text(person.name);
}
這可能導致widget在 name
以外的屬性發(fā)生變化時重構建瞬捕。
你可以使用 context.select
來 只監(jiān)聽name
屬性
Widget build(BuildContext context) {
final name = context.select((Person p) => p.name);
return Text(name);
}
這樣,這widget間就不會在name
以外的屬性變化時進行不必要的重構建了舵抹。
同樣肪虎,你也可以使用Consumer/Selector,可選的child
參數(shù)使得widget樹中只有所指定的一部分會重構建掏父。
Foo(
child: Consumer<A>(
builder: (_笋轨, a, child) {
return Bar(a: a赊淑, child: child);
}爵政,
child: Baz(),
)陶缺,
)
在這個示例中钾挟, 只有Bar
會在A
更新時重構建,Foo
與Baz
不會進行不必要的重構建饱岸。
我能使用相同類型來獲得兩個不同的provider嗎?
不掺出。 當你有兩個持有相同類型的不同provider時,一個widget只會獲取其中之一: 最近的一個苫费。
你必須顯式為兩個provider提供不同類型汤锨,而不是:
Provider<String>(
create: (_) => 'England',
child: Provider<String>(
create: (_) => 'London'百框,
child: ...闲礼,
),
)铐维,
推薦的寫法:
Provider<Country>(
create: (_) => Country('England')柬泽,
child: Provider<City>(
create: (_) => City('London'),
child: ...嫁蛇,
)锨并,
),
我能消費一個接口并且提供一個實現(xiàn)嗎?
能睬棚,類型提示(type hint
)必須被提供給編譯器第煮,來指定將要被消費的接口,同時需要在craete
中提供具體實現(xiàn):
abstract class ProviderInterface with ChangeNotifier {
...
}
class ProviderImplementation with ChangeNotifier implements ProviderInterface {
...
}
class Foo extends StatelessWidget {
@override
build(context) {
final provider = Provider.of<ProviderInterface>(context);
return ...
}
}
ChangeNotifierProvider<ProviderInterface>(
create: (_) => ProviderImplementation()抑党,
child: Foo()空盼,
),
現(xiàn)有的providers
provider
中提供了幾種不同類型的"provider"新荤,供不同類型的對象使用揽趾。
完整的可用列表參見 provider-library
name | description |
---|---|
Provider | 最基礎的provider組成,接收一個值并暴露它苛骨, 無論值是什么篱瞎。 |
ListenableProvider | 供可監(jiān)聽對象使用的特殊provider,ListenableProvider會監(jiān)聽對象痒芝,并在監(jiān)聽器被調用時更新依賴此對象的widgets俐筋。 |
ChangeNotifierProvider | 為ChangeNotifier提供的ListenableProvider規(guī)范,會在需要時自動調用ChangeNotifier.dispose 严衬。 |
ValueListenableProvider | 監(jiān)聽ValueListenable澄者,并且只暴露出ValueListenable.value 。 |
StreamProvider | 監(jiān)聽流,并暴露出當前的最新值粱挡。 |
FutureProvider | 接收一個Future 赠幕,并在其進入complete狀態(tài)時更新依賴它的組件。 |