Provider 5.0

  • 新的 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來提供一個當前已存在的 ChangeNotifier

    MyChangeNotifier 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>(),
    );
  }
}

或者不使用這些方法被啼,我們也可以使用 ConsumerSelector帜消。

這些往往在性能優(yōu)化以及當很難獲取到provider的構建上下文后代(difficult to obtain a BuildContext descendant of the provider) 時是很有用的。

參見 FAQ 或關于ConsumerSelector 的文檔部分了解更多.

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 vs ProxyProvider2 vs ProxyProvider3摧冀, ...

    類名后的數(shù)字是 ProxyProvider 依賴的其他providers的數(shù)量

  • ProxyProvider vs ChangeNotifierProxyProvider vs ListenableProxyProvider倍踪, ...

    它們工作的方式是相似的, 但 ChangeNotifierProxyProvider 會將它的值傳遞給ChangeNotifierProvider 而非 Provider索昂。

FAQ

我是否能查看(inspect)我的對象的內容?

Flutter提供的開發(fā)者工具能夠展示特定時刻下的widget樹建车。

既然providers同樣是widget,他們同樣能通過開發(fā)者工具進行查看椒惨。

img

點擊一個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更新時重構建,FooBaz不會進行不必要的重構建饱岸。

我能使用相同類型來獲得兩個不同的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)時更新依賴它的組件。
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末询筏,一起剝皮案震驚了整個濱河市榕堰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嫌套,老刑警劉巖逆屡,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異踱讨,居然都是意外死亡魏蔗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門痹筛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來莺治,“玉大人,你說我怎么就攤上這事味混〔ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵翁锡,是天一觀的道長蔓挖。 經(jīng)常有香客問我,道長馆衔,這世上最難降的妖魔是什么瘟判? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮角溃,結果婚禮上拷获,老公的妹妹穿的比我還像新娘。我一直安慰自己减细,他們只是感情好匆瓜,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著未蝌,像睡著了一般驮吱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上萧吠,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天左冬,我揣著相機與錄音,去河邊找鬼纸型。 笑死拇砰,一個胖子當著我的面吹牛梅忌,可吹牛的內容都是我干的。 我是一名探鬼主播除破,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼牧氮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了皂岔?” 一聲冷哼從身側響起蹋笼,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤展姐,失蹤者是張志新(化名)和其女友劉穎躁垛,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體圾笨,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡教馆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了擂达。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片土铺。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖板鬓,靈堂內的尸體忽然破棺而出悲敷,到底是詐尸還是另有隱情,我是刑警寧澤俭令,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布后德,位于F島的核電站,受9級特大地震影響抄腔,放射性物質發(fā)生泄漏瓢湃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一赫蛇、第九天 我趴在偏房一處隱蔽的房頂上張望绵患。 院中可真熱鬧,春花似錦悟耘、人聲如沸落蝙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽筏勒。三九已至,卻和暖如春粟誓,著一層夾襖步出監(jiān)牢的瞬間奏寨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工鹰服, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留病瞳,地道東北人揽咕。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像套菜,于是被迫代替她去往敵國和親亲善。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內容