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完成时更新附从。
相关文章
相关标签/搜索