-
新的 create/update 回调函数是懒加载的, 也就是说他们在对应的值第一次被读取时才被调用, 而非provider首次被创建时.
如果你不需要这个特性, 你可以通过将provider的lazy属性置为false, 来禁用懒加载
FutureProvider( create: (_) async => doSomeHttpRequest(), lazy: false, child: ... )
ProviderNotFoundError
更名为ProviderNotFoundException
.-
SingleChildCloneableWidget
接口被移除, 并被全新类型的组件SingleChildWidget
所替代参考这个 issue 来获取迁移细节.
-
Selector 现在会将先后的集合类型的值进行深层对比
如果你不需要这个特性, 你可以通过
shouldRebuild
参数来使其还原至旧有表现.Selector<Selected, Consumed>( shouldRebuild: (previous, next) => previous == next, builder: ..., )
DelegateWidget
及其家族widget被移除, 现在想要自定义provider, 直接继承 InheritedProvider 或当前存在的provider.
使用
暴露一个值
暴露一个新的对象实例
Providers不仅允许暴露出一个值,也可以创建/监听/销毁它。
要暴露一个新创建的对象, 使用一个provider的默认构造函数. 如果你想创建一个对象, 不要使用 .value
构造函数, 否则可能会有你预期外的副作用。
查看该 StackOverflow Answer,来了解更多为什么不要使用.value
构造函数创建值。
-
在create内创建新对象
Provider( create: (_) => MyModel(), child: ... )
-
不要使用
Provider.value
创建对象ChangeNotifierProvider.value( value: MyModel(), child: ... )
-
不要以可能随时间改变的变量创建对象
在这种情况下,如果变量发生变化,你的对象将永远不会被更新
int count; Provider( create: (_) => MyModel(count), child: ... )
如果你想将随时间改变的变量传入给对象,请使用
ProxyProvider
:int count; ProxyProvider0( update: (_, __) => MyModel(count), child: ... )
注意:
在使用一个provider的create
/update
回调时,请注意回调函数默认是懒调用的。
也就是说, 除非这个值被读取了至少一次, 否则create
/update
函数不会被调用。
如果你想预先计算一些逻辑, 可以通过使用lazy
参数来禁用这一行为。
MyProvider(
create: (_) => Something(),
lazy: false,
)
复用一个已存在的对象实例:
如果你已经拥有一个对象实例并且想暴露出它,你应当使用一个provider的.value
构造函数。
如果你没有这么做,那么在你调用对象的 dispose
方法时, 这个对象可能仍然在被使用。
-
使用
ChangeNotifierProvider.value
来提供一个当前已存在的 ChangeNotifierMyChangeNotifier variable; ChangeNotifierProvider.value( value: variable, child: ... )
-
不要使用默认的构造函数来尝试复用一个已存在的 ChangeNotifier
MyChangeNotifier variable; ChangeNotifierProvider( create: (_) => variable, child: ... )
读取一个值
读取一个值最简单的方式就是使用BuildContext
上的扩展属性(由provider
注入)。
-
context.watch<T>()
, 一方法使得widget能够监听泛型T
上发生的改变。 -
context.read<T>()
,直接返回T
,不会监听改变。 -
context.select<T, R>(R cb(T value))
,允许widget只监听T
上的一部分(R
)。
或者使用 Provider.of<T>(context)
这一静态方法,它的表现类似 watch
,而在你为 listen
参数传入 false
时(如 Provider.of<T>(context,listen: false)
),它的表现类似于 read
。
值得注意的是,context.read<T>()
方法不会在值变化时使得widget重新构建, 并且不能在 StatelessWidget.build
/State.build
内调用. 换句话说, 它可以在除了这两个方法以外的任意之处调用。
上面列举的这些方法会与传入的 BuildContext
关联的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>(),
);
}
}
或者不使用这些方法,我们也可以使用 Consumer 与 Selector。
这些往往在性能优化以及当很难获取到provider的构建上下文后代(difficult to obtain a BuildContext
descendant of the provider) 时是很有用的。
参见 FAQ 或关于Consumer 和 Selector 的文档部分了解更多.
MultiProvider
当在大型应用中注入较多状态时, 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,
)
以上两个例子的实际表现是一致的, 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
vsProxyProvider2
vsProxyProvider3
, ...类名后的数字是
ProxyProvider
依赖的其他providers的数量 -
ProxyProvider
vsChangeNotifierProxyProvider
vsListenableProxyProvider
, ...它们工作的方式是相似的, 但
ChangeNotifierProxyProvider
会将它的值传递给ChangeNotifierProvider
而非Provider
。
FAQ
我是否能查看(inspect)我的对象的内容?
Flutter提供的开发者工具能够展示特定时刻下的widget树。
既然providers同样是widget,他们同样能通过开发者工具进行查看。
点击一个provider, 即可查看它暴露出的值:
[图片上传失败...(image-6c27fc-1623978187784)]
以上的开发者工具截图来自于 /example
文件夹下的示例
开发者工具只显示"Instance of MyClass", 我能做什么?
默认情况下, 开发者工具基于toString
,也就使得默认结果是 "Instance of MyClass"。
如果要得到更多信息,你有两种方式:
-
使用Flutter提供的 Diagnosticable API
在大多数情况下, 只需要在你的对象上使用 DiagnosticableTreeMixin 即可,以下是一个自定义 debugFillProperties 实现的例子:
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 要更简单,但能力也有着不足: 你无法 展开/折叠 来查看你的对象内部细节。
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时发生了异常, 该做什么?
这个异常的出现是因为你在尝试监听一个来自于永远不会再次被调用的生命周期的provider。
这意味着你要么使用另外一个生命周期(build
),要么显式指定你并不在意后续更新。
也就是说,不应该这么做:
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,并且会忽视后续的更新
如何控制我的对象上的热更新?
你可以使你提供的对象实现 ReassembleHandler
类:
class Example extends ChangeNotifier implements ReassembleHandler {
@override
void reassemble() {
print('Did hot-reload');
}
}
通常会和 provider
一同使用:
ChangeNotifierProvider(create: (_) => Example()),
使用ChangeNotifier时, 在更新后出现了异常, 发生了什么?
这通常是因为你在widget树正在构建时,从ChangeNotifier的某个后代更改了ChangeNotifier。
最典型的情况是在一个future被保存在notifier内部时发起http请求。
initState() {
super.initState();
context.read<MyNotifier>().fetchSomething();
}
这是不被允许的,因为更改会立即生效.
也就是说,一些widget可能在变更发生前构建,而有些则可能在变更后. 这可能造成UI不一致, 因此是被禁止的。
所以,你应该在一个整个widget树所受影响相同的位置执行变更:
-
直接在你的model的 provider/constructor 的
create
方法内调用:class MyNotifier with ChangeNotifier { MyNotifier() { _fetchSomething(); } Future<void> _fetchSomething() async {} }
在不需要传入形参的情况下,这是相当有用的。
-
在框架的末尾异步的执行(
Future.microtask
):initState() { super.initState(); Future.microtask(() => context.read<MyNotifier>(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(context.watch<int>().toString());
并且这样来修改状态:
return FloatingActionButton(
onPressed: () => context.read<ExampleState>().increment(),
child: Icon(Icons.plus_one),
);
或者你还可以自定义provider.
我可以创建自己的Provider吗?
可以,provider
暴露出了所有构建功能完备的provider所需的组件,它包含:
-
SingleChildStatelessWidget
, 使任意widget能够与MultiProvider
协作, 这个接口被暴露为包package:provider/**single_child_widget
的一部分** -
InheritedProvider,在使用
context.watch
时可获取的通用InheritedWidget
。
这里有个使用 ValueNotifier
作为状态的自定义provider例子:
https://gist.github.com/rrousselGit/4910f3125e41600df3c2577e26967c91
我的widget重构建太频繁了, 我能做什么?
你可以使用 context.select
而非 context.watch
来指定只监听对象的部分属性:
举例来说,你可以这么写:
Widget build(BuildContext context) {
final person = context.watch<Person>();
return Text(person.name);
}
这可能导致widget在 name
以外的属性发生变化时重构建。
你可以使用 context.select
来 只监听name
属性
Widget build(BuildContext context) {
final name = context.select((Person p) => p.name);
return Text(name);
}
这样,这widget间就不会在name
以外的属性变化时进行不必要的重构建了。
同样,你也可以使用Consumer/Selector,可选的child
参数使得widget树中只有所指定的一部分会重构建。
Foo(
child: Consumer<A>(
builder: (_, a, child) {
return Bar(a: a, child: child);
},
child: Baz(),
),
)
在这个示例中, 只有Bar
会在A
更新时重构建,Foo
与Baz
不会进行不必要的重构建。
我能使用相同类型来获得两个不同的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: ...,
),
),
我能消费一个接口并且提供一个实现吗?
能,类型提示(type hint
)必须被提供给编译器,来指定将要被消费的接口,同时需要在craete
中提供具体实现:
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(),
),
现有的providers
provider
中提供了几种不同类型的"provider",供不同类型的对象使用。
完整的可用列表参见 provider-library
name | description |
---|---|
Provider | 最基础的provider组成,接收一个值并暴露它, 无论值是什么。 |
ListenableProvider | 供可监听对象使用的特殊provider,ListenableProvider会监听对象,并在监听器被调用时更新依赖此对象的widgets。 |
ChangeNotifierProvider | 为ChangeNotifier提供的ListenableProvider规范,会在需要时自动调用ChangeNotifier.dispose 。 |
ValueListenableProvider | 监听ValueListenable,并且只暴露出ValueListenable.value 。 |
StreamProvider | 监听流,并暴露出当前的最新值。 |
FutureProvider | 接收一个Future ,并在其进入complete状态时更新依赖它的组件。 |