provider
可以进行依赖注入和状态管理,使用widget创建,适用于widget。
它是故意设计成使用widget来进行依赖注入和状态管理的,而不是纯使用dart类,像是stream这些。因为widget简单且健壮可伸缩。
使用weidget来进行状态管理可以保证。
- 维护性。
- 可测试和兼容性。
- 健壮性。
使用
暴露一个值
暴露一个对象实例(object instance)
除了暴露一个值的可访问性,provider还包括这个值的创建,监听,销毁。
为了暴露一个新创建的对象,可以使用provider的默认构造函数。不要使用.value
命名构造函数类创建一个值对象,不然可能会造成其他不期望影响。
- 可做
create
中创建一个对象。
Provider(
create:(_)=>new MyModel(),
child:...
)
- 不可做 使用
Provider.value
命名构造函数创建一个对象。
ChangeNotifierProvider.value(
value:new MyModel(),
child:...
)
如果创建了一个对象,它使用了为了可能变更的变量做参数,那么考虑使用ProxyProvider:
int count;
ProxyProvider0(
update:L(_,__)=> new MyModel(count),
child:...
)
复用一个已经存在的对象实例
如果有个对象实例,你想把其暴露在其他地方使用,那么你应该使用Provider的.value
命名构造函数。
不这么做可能会造成在该对象还在使用时被dispose
。
- 可做 使用
ChangeNotifierProvider.value
来提供一个已经存在的ChangeNotifier
。
MyChangeNotifier varibale;
ChangeNotifierProvider.value(
value:variable,
child:...
)
- 不可做 在默认构造函数中重用
CahangeNotifier
。
MyChangeNotifier variable;
ChangeNotifierProvider(
create:(_)=>variable,
child:...
)
读取一个值(获取暴露的值)
获取一个值最简单的方法是使用静态方法Provider.of
。
这个方法会从当前的context在widget树中向根widget方向查找符合类型T的最近的值。(如果没有找到就throw)。
除了Provider.of
方法我们也可以使用Consumer
和Selector
两个widget。
这对于高效的组织代码以及难以获取BuildContext
的情况比较有帮助。
多个Provider的情况(嵌套)/MultiProvider
当在一个较大的应用中注入较多的数据时,Provider会飞快地嵌套多层。
Provider(
create:(_)=>Something(),
child: Provider(
create:(_)=>SomethingElse(),
child:Provider(
create:(_)=>AnotherTing(),
child:someWidget,
)
)
)
可以这样写
MultiProvider(
Providers:[
Provider(create:(_)=>Something()),
Provider(create:(_)=>SomethingElse()),
Provider(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(
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
vsProxyProvider2
vsProxyProvider3
...
类名后的数字是指ProxyProvider
依赖其他Provider的数量。
-
ProxyProvider
vsChangeNotifierProxyProvider
vs ListenableProxyProvider,...他们的工作方式是类似的,相对于发送结果给一个Provider,一个ChangeNotifierProxyProvider
会发送给一个ChangeNotifierProxyProvider
。
问答
在InitState中获取Provider发生了错误,怎么办?
这个错误是因为你想监听一个在其生命周期中不会被再次调用的provider。
这说明你不会再使用其他的生命周期(didChangeDependencies/build),或者你不在乎数据更新。
不要这么做
initState(){
super.initState();
print(Provider.of(context).value);
}
你可以这么做
Value value;
didChangeDependencies(){
super.didChangeDependencies();
final value = Provider.of(context).value;
if(value != this.value){
this.value = value;
print(value);
}
}
每当值发生了变化,都会被打印。
也可以这么做
initState(){
super.initState();
print(Provider.of(context,listen:false).value);
}
这样只会打印value一次,不再更新。
我使用了ChangeNotifier
,当我更新数据时发生了错误,发生了什么?
这个经常发生在widget树正在构建时,你对ChangeNotifier进行了更改操作。
一个典型的情景是,发起了一个http请求,然后该future被保存在了notifer中。
initState(){
super.initState();
Provider.of(context).fetchSomething();
}
这样是禁止的,因为更改必须是立即的。
这意味着有些widget可能在变动之前build,然而其他的在变动之后build。这可能会造成你的ui发生冲突,所以是禁止的。
相比,你可以在整个widget树都同步之后(渲染前/选然后?)进行变动。
- 直接在你的模型之内进行创建:
class Mymodel width ChangeNotifier{
MyModel(){
_fetchSomething();
}
Future _fetchSomething()async {}
}
这个适用于没有额外参数的情况。
- 一部发生在最后一帧:
initState(){
super.initState();
Future.microtash(()=>{
Provider.of(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{
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(context).toString());
如此更改数据:
return FloatingActionButton(
onPress:Provider.of(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(
builder:(_,a,child){
return Bar(a:a,child:child);
}
child: Baz(),
),
)
这个例子中只有Bar会在A更新时被重建,Foo和Baz非必要下不会更新。
更深一步,使用selector来忽略widget树中一些没有影响的更新也是可能的。
Selector(
selector:(_,list)=>list.length,
builder:(_,length,__){
return Text('$length');
}
);
这个代码片段中,只有list的length变化时,才会被重构。即使一个item发生了变化也不会更新。
我能够使用同样的类型获取两个不同的provider吗?
不能。
你可以使用多个Provider共享同样的类型,一个widget只能获取到他们中的一个:最近的那个。
不然,你必须给与不同的provider不同的数据类型。
相较:
Provider(
create:(_)=>'england',
child:Provider(
create:(_)=>'London',
child:...
)
)
推荐:
Provider(
create:(_)=>'england',
child:Provider(
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完成时更新附从。 |