Flutter狀態管理Provider詳解

provider

能夠進行依賴注入和狀態管理,使用widget建立,適用於widget。async

它是故意設計成使用widget來進行依賴注入和狀態管理的,而不是純使用dart類,像是stream這些。由於widget簡單且健壯可伸縮。ide

使用weidget來進行狀態管理能夠保證。函數

  1. 維護性。
  2. 可測試和兼容性。
  3. 健壯性。

使用

暴露一個值

暴露一個對象實例(object instance)

除了暴露一個值的可訪問性,provider還包括這個值的建立,監聽,銷燬。測試

爲了暴露一個新建立的對象,可使用provider的默認構造函數。不要使用.value命名構造函數類建立一個值對象,否則可能會形成其餘不指望影響。fetch

  • 可作 create中建立一個對象。
Provider(
  create:(_)=>new MyModel(),
  child:...
)
  • 不可作 使用Provider.value命名構造函數建立一個對象。
ChangeNotifierProvider.value(
  value:new MyModel(),
  child:...
)

若是建立了一個對象,它使用了爲了可能變動的變量作參數,那麼考慮使用ProxyProvider:ui

int count;

ProxyProvider0(
  update:L(_,__)=> new MyModel(count),
  child:...
)

複用一個已經存在的對象實例

若是有個對象實例,你想把其暴露在其餘地方使用,那麼你應該使用Provider的.value命名構造函數。this

不這麼作可能會形成在該對象還在使用時被dispose設計

  • 可作 使用ChangeNotifierProvider.value來提供一個已經存在的ChangeNotifier
MyChangeNotifier varibale;

ChangeNotifierProvider.value(
  value:variable,
  child:...
)
  • 不可作 在默認構造函數中重用CahangeNotifier
MyChangeNotifier variable;

ChangeNotifierProvider(
  create:(_)=>variable,
  child:...
)

讀取一個值(獲取暴露的值)

獲取一個值最簡單的方法是使用靜態方法Provider.of<T>(BuildContext context)code

這個方法會從當前的context在widget樹中向根widget方向查找符合類型T的最近的值。(若是沒有找到就throw)。對象

除了Provider.of方法咱們也可使用ConsumerSelector兩個widget。
這對於高效的組織代碼以及難以獲取BuildContext的狀況比較有幫助。

多個Provider的狀況(嵌套)/MultiProvider

當在一個較大的應用中注入較多的數據時,Provider會飛快地嵌套多層。

Provider<Something>(
  create:(_)=>Something(),
  child: Provider<SomethingElse>(
    create:(_)=>SomethingElse(),
    child:Provider<AnotherThing>(
      create:(_)=>AnotherTing(),
      child:someWidget,
    )
  )
)

能夠這樣寫

MultiProvider(
  Providers:[
    Provider<Somthing>(create:(_)=>Something()),
    Provider<SomthingElse>(create:(_)=>SomethingElse()),
    Provider<AnotherThing>(create:(_)=>AnotherTthing()),
  ],
  child:someWidget
)

上面代碼的結果是嚴格的同樣的。MultiProvider僅僅是改變了代碼的形式。

ProxyProvider

從版本3.0.0開始增長了一個新的Provider:ProxyProvider。

ProxyProvider自己是一個Provider,它把其餘多個provider的數據結合成一個新的對象,而且把這個結果發送一個一個Provider。

被結合的的這些provider中的任何一個數據更新了,這個新的對象都會更新。

下面這個例子使用了ProxyProvider,他把其餘provider中的counter作了箇中轉。

Widget build(BuildContext context){
  return MultiProvider(
    providers:[
      ChangeNotifierProvider(create:(_)=>Counter()),
      ProxyProvider<Counter,Translations>(
        create:(_,counter,__)=>Translations(clunter.value),
      ),
      child:Foo()
    ]
  )
}

class Translations{
  const Translations(this._value);

  final init _value;

  String get title=>'You clicked: $_value times';
}

ProxyProvider有不少種變體,例如:

  • ProxyProvider vs ProxyProvider2 vs ProxyProvider3...

類名後的數字是指ProxyProvider依賴其餘Provider的數量。

  • ProxyProvider vs ChangeNotifierProxyProvider vs ListenableProxyProvider,...他們的工做方式是相似的,相對於發送結果給一個Provider,一個ChangeNotifierProxyProvider會發送給一個ChangeNotifierProxyProvider

問答

在InitState中獲取Provider發生了錯誤,怎麼辦?

這個錯誤是由於你想監聽一個在其生命週期中不會被再次調用的provider。

這說明你不會再使用其餘的生命週期(didChangeDependencies/build),或者你不在意數據更新。
不要這麼作

initState(){
  super.initState();
  print(Provider.of<Foo>(context).value);
}

你能夠這麼作

Value value;

didChangeDependencies(){
  super.didChangeDependencies();
  final value = Provider.of<Foo>(context).value;
  if(value != this.value){
    this.value = value;
    print(value);
  }
}

每當值發生了變化,都會被打印。
也能夠這麼作

initState(){
  super.initState();
  print(Provider.of<Foo>(context,listen:false).value);
}

這樣只會打印value一次,再也不更新。

我使用了ChangeNotifier,當我更新數據時發生了錯誤,發生了什麼?

這個常常發生在widget樹正在構建時,你對ChangeNotifier進行了更改操做。

一個典型的情景是,發起了一個http請求,而後該future被保存在了notifer中。

initState(){
  super.initState();
  Provider.of<Foo>(context).fetchSomething();
}

這樣是禁止的,由於更改必須是當即的。

這意味着有些widget可能在變更以前build,然而其餘的在變更以後build。這可能會形成你的ui發生衝突,因此是禁止的。

相比,你能夠在整個widget樹都同步以後(渲染前/選而後?)進行變更。

  • 直接在你的模型以內進行建立:
class Mymodel width ChangeNotifier{
  MyModel(){
    _fetchSomething();
  }
  
  Future<void> _fetchSomething()async {}
}

這個適用於沒有額外參數的狀況。

  • 一部發生在最後一幀:
initState(){
  super.initState();
  Future.microtash(()=>{
    Provider.of<Foo>(context).fetchSomething(someValue);
  })
}

這個多少是不太理想的,可是容許傳入參數進行變動。

對於複雜的狀態我是否必須使用ChangeNotifier?

不是。

你可使用任何對象來呈現狀態。例如其餘可用的方式是Provider.value()結合一個StatefulWidget使用。

這裏有個計數的例子,使用了這個方法:

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
      )
    )
  }
}

能夠如此讀取數據:

return Text(Provider.of<int>(context).toString());

如此更改數據:

return FloatingActionButton(
  onPress:Provider.of<Examp0leState>(context).increment,
  child:Icon(Icons.plus_one),
);

此外,你也能夠建立本身的provider。

我製做一個本身的Provider嗎?

固然,provider暴露了全部的小的組件,這些製做了一個簡陋的provider。

包括:

  • SingleChildCloneableWidget,可用來建立任何配個MultiProvider工做的widget。
  • InheritedProvider,通用的InheritedWidget,使用Provider.of來獲取。
  • DelegateWidget/BuilderDelegate/ValueDelegate幫助處理"MyProvider() 建立一個對象" vs 」Myprovider.value() 隨時間更新"的邏輯。

個人widget build太頻繁,怎麼辦?

相較於Provider.of,你可使用Consumer/Selector。

他們可選的child參數只容許重建widget中很是小的具體部分。

Foo(
  child:Consumer<A>(
    builder:(_,a,child){
      return Bar(a:a,child:child);
    }
    child: Baz(),
  ),
)

這個例子中只有Bar會在A更新時被重建,Foo和Baz非必要下不會更新。

更深一步,使用selector來忽略widget樹中一些沒有影響的更新也是可能的。

Selector<List,int>(
  selector:(_,list)=>list.length,
  builder:(_,length,__){
    return Text('$length');
  }
);

這個代碼片斷中,只有list的length變化時,纔會被重構。即便一個item發生了變化也不會更新。

我可以使用一樣的類型獲取兩個不一樣的provider嗎?

不能。
你可使用多個Provider共享一樣的類型,一個widget只能獲取到他們中的一個:最近的那個。

否則,你必須給與不一樣的provider不一樣的數據類型。

相較:

Provider<String>(
  create:(_)=>'england',
  child:Provider<String>(
    create:(_)=>'London',
    child:...
  )
)

推薦:

Provider<Country>(
  create:(_)=>'england',
  child:Provider<City>(
    create:(_)=>'London',
    child:...
  )
)

現有的providers

provider包提供了一些不一樣類新的'provider'應對不一樣類型的對象。
以下:

名字 說明
Provider provider的最基本形式,能夠添加和暴露任何形式的值
ListenableProvider 使用Listenable對象的特殊provider,ListenableProvider會監聽對象,而且在監聽器在任什麼時候候調用時要求widget重構。
ChangeNotifierProvider ChangeNotifier規格的ListenableProvider,他會在必要時自動調用ChangeNotifier.dispose.
ValueListenableProvider 監聽一個ValueListenable而且只暴露ValueListenable.value。
StreamProvider 監聽Stream而且暴露最新的emitted的值。
FutureProvider 添加一個Future,而且在future完成時更新附從。
相關文章
相關標籤/搜索