Flutter | 狀態管理特別篇——Provide

前言

今天偶然發如今谷歌爸爸的倉庫下出現了一個叫作flutter-provide的狀態管理框架,2月8日才第一次提交,很是新鮮。在簡單上手以後感受就是一個字——爽!因此今天就跟你們分享一下這個新的狀態管理框架。git

Provider被設計爲ScopedModel的替代品,而且容許咱們更加靈活地處理數據類型和數據。可是首先呢仍是先說說老生常談的狀態管理。github

爲何須要狀態管理

在咱們一開始構建應用的時候,也許很簡單。咱們有一些狀態,直接把他們映射成視圖就能夠了。這種簡單應用可能並不須要狀態管理。redux

可是隨着功能的增長,你的應用程序將會有幾十個甚至上百個狀態。這個時候你的應用應該會是這樣。架構

Wow,這是什麼鬼。咱們很難再清楚的測試維護咱們的狀態,由於它看上去實在是太複雜了!並且還會有多個頁面共享同一個狀態,例如當你進入一個文章點贊,退出到外部縮略展現的時候,外部也須要顯示點贊數,這時候就須要同步這兩個狀態。app

這時候,咱們便迫切的須要一個架構來幫助咱們理清這些關係,狀態管理框架應運而生。框架

什麼是Provide

和Scoped_model同樣,Provide也是藉助了InheritWidget,將共享狀態放到頂層MaterialApp之上。底層部件經過Provier獲取該狀態,並經過混合ChangeNotifier通知依賴於該狀態的組件刷新。異步

Provide還提供了Provide.stream,讓咱們可以以處理流的方式處理數據,不過目前還有一些問題,不推薦使用。ide

Lets do it!

咱們這裏仍是以一個簡單app爲例,詳細介紹Provide的用法。其中涉及共享狀態以及多個狀態之間如何管理。post

這兩個頁面都同時依賴於counter 和 switcher兩個不一樣的狀態。而且一個頁面改變狀態以後另一個頁面狀態也隨之改變。性能

該項目完整代碼已放在 Github

第一步:添加依賴

在pubspec.yaml中添加Provide的依賴。

第二步:建立Model

這裏實際上它承擔了State的職責,可是爲了和官方的State區分因此叫作model。

import 'package:flutter/material.dart';

class Counter with ChangeNotifier{
  int value = 0;
  
  increment(){
    value++;
    notifyListeners();
  }
}

這裏咱們能夠看到,數據和操做數據的方法都在model中,咱們能夠很清晰的把業務分離出來。

對比Scoped_model能夠發現,Provide模式中model再也不須要繼承Model類,只須要實現Listenable,咱們這裏混入ChangeNotifier,能夠不用管理聽衆。

經過 notifyListeners 咱們能夠通知聽衆刷新。

第三步:將狀態放入頂層

void main() {
  var counter = Counter();
  var providers = Providers();

//將counter對象添加進providers
  providers.provide(Provider<Counter>.value(counter));

  runApp(
    ProviderNode(
        child: MyApp(), 
        providers: providers),
    );
}

ProviderNode封裝了InheritWidget,而且提供了 一個providers容器用於放置狀態。

ProviderScope 爲Provider提供單獨的類型空間,它容許多個相同類型的提供者。默認使用ProviderScope('_default'),存放的時候你能夠經過ProviderScope("name")來指定key。

添加一組Provider的時候建議使用provideFrom或者provide方法,而不是provideAll,由於它能夠檢查編譯時的類型錯誤。

Provider<Counter>.value將counter包裝成了_ValueProvider。並在它的內部提供了StreamController從而實現對數據進行流式操做。

第四步:獲取狀態

一樣的Provide也提供了兩種獲取State的方法。咱們先來介紹第一種,經過Provide<T>小部件獲取。

Provide<Counter>(
              builder: (context, child, counter) {
                return Text(
                  '${counter.value}',
                  style: Theme.of(context).textTheme.display1,
                );
              },
            ),

每次通知數據刷新時,builder將會從新構建這個小部件。

builder方法接收三個參數,這裏主要介紹第二個和第三個。

  • 第二個參數child:假如這個小部件足夠複雜,內部有一些小部件是不會改變的,那麼咱們能夠將這部分小部件寫在Provide的child屬性中,讓builder再也不重複建立這些小部件,以提高性能。
  • 第三個參數counter:這個參數表明了咱們獲取的頂層providers中的狀態<T>。

scope:經過指定ProviderScope獲取該鍵所對應的狀態。在須要使用多個相同類型狀態的時候使用。

第二種獲取方式:Provide.value<T>(context)

final currentCounter = Provide.value<Counter>(context);

這種方式實際上調用了context.inheritFromWidgetOfExactType找到頂層的_InheritedProviders來獲取到頂層providers中的狀態。

如何組織多個狀態

和scoped_model不一樣的是,provide模式中你能夠輕鬆組織多個狀態。只須要將狀態provide進provider中就能夠了。

void main() {
  var counter = Counter();
  var switcher = Switcher();

  var providers = Providers();

  providers
    ..provide(Provider<Counter>.value(counter))
    ..provide(Provider<Switcher>.value(switcher));

  runApp(
    ProviderNode(
        child: MyApp(), 
        providers: providers)
    );
}

獲取數據流

在將counter添加進providers的過程當中進行了一次包裝。咱們剛纔經過分析源碼知道了這個操做可以讓咱們處理流式數據。

經過 Provide.stream<T>(context) 就能獲取數據流。須要注意的是,這裏每次獲取的數據流都

StreamBuilder<Counter>(
          initialData: currentCounter,
          stream: Provide.stream<Counter>(context)
              .where((counter) => counter.value % 2 == 0),
          builder: (context, snapshot) =>
              Text('Last even value: ${snapshot.data.value}')),

不過在個人使用當中出現了streamTransformer失效的狀況。在firstScreen和secondScreen一樣應用這一段相同的代碼,second screen的where方法可以生效,過濾掉奇數數據,而first screen中則是收到了完整的數據。

須要注意的是,這裏每次獲取的數據流都會從新建立一條新的流。

/// Creates a provider that listens to a stream and caches the last
  /// received value of the stream.
  /// This provider notifies for rebuild after every release.
  factory Provider.stream(Stream<T> stream, {T initialValue}) =>
      _StreamProvider<T>(stream, initialValue: initialValue);

關於這個作法還有一些爭議,具體能夠查看這個issue:
https://github.com/google/flutter-provide/issues/3

不過這個功能還能夠結合rxdart使用,能夠經過stream輕鬆構建Observer,讓咱們更加靈活的組織數據。

根據多個狀態重建小部件

當咱們一個視圖可能依賴於多個狀態進行重建的時候,可使用ProvideMulti小部件。

寫在最後

自從上次寫完狀態管理拓展篇Rxdart以後斷更了三個月。總結篇遲遲沒有出來,在這裏先說一聲抱歉。對於我來說狀態管理這個自己就是一個新鮮玩意,因此在沒有通過大型應用實戰檢驗的總結都是空談。 這也是爲何我遲遲沒有開始寫總結篇的緣由。不過在這我能夠說一些本身的感覺,供你們參考。

在這幾個月中,我用的比較多的是BLoC,它組織數據確實很是靈活,能夠很輕鬆的實現懶加載之類的操做。並且stateful widget寫的是愈來愈少了。缺點就是入門的門檻比較高,理解StreamTransformer和爲何須要pipe花了我很多時間。使用bloc思惟方式須要比較大的改變,我看到了許多人在項目中使用bloc,可是用得很奇怪,還在以以前的思惟模式思考。並且bloc只是對數據進行組織,共享狀態平時仍是使用的InheritWidget,確實要作不少額外的功夫。

其次我比較喜歡的就是scoped_model,理由就是簡單好用。學習成本很低,並且沒有寫什麼模版代碼。

我最不想使用的狀態管理方式就是redux了,一個是入門難度比較高,並且對於異步數據處理我也以爲是至關麻煩的。可是閒魚團隊卻是喜歡redux,以後還會開源閒魚的狀態管理框架fish_redux。因此說,可能仍是我編寫的應用還不夠複雜,纔會有這種感覺。redux在複雜應用上可以更加清楚的劃分職責,而且單向數據流以及state是immutable的特色這些都是redux的好處。

最後我再談談Provide。Provide總體上給個人體驗很是接近Scoped,簡單易上手,而且更增強大。model不用再繼承,只用實現Listenable讓它再也不具備侵入性。於此同時又增長了stream的特性,和bloc的作法又有幾分類似。若是你使用過Scoped_model你會很快就上手。

不過能夠說的是,Provide是一個很是優秀的狀態管理方式,值得你去使用。可是目前該package還存在一些問題,例如Provide.stream,在將來可能會進行較大的變更,須要慎重使用。

本次代碼已上傳Github: https://github.com/OpenFlutter/Flutter-Notebook/tree/master/mecury_project/example/flutter_provide

若是您對Provide還有任何疑問或者文章的建議,歡迎在下方評論區以及個人郵箱1652219550a@gmail.com與我聯繫,我會及時回覆!

相關文章
相關標籤/搜索