provider
一个 InheritedWidget 的包装器,使其更易于使用和重用。
通过使用 provider 而不是手动编写 InheritedWidget,您可以获得
- 简化的资源分配/处理
- 懒加载
- 每次创建新类时的样板代码大幅减少
- 对开发工具友好
- 一种消费这些 InheritedWidget 的通用方法(请参阅 Provider.of/Consumer/Selector)
- 增加了具有指数级增长的监听机制的类的可扩展性
在复杂性方面(例如 ChangeNotifier,其通知分发是 O(N) 的)。
要了解有关 provider 的更多信息,请参阅其 文档。
另请参阅
- Flutter 状态管理官方文档,其中展示了如何使用
provider+ ChangeNotifier - Flutter 架构示例,其中包含使用
provider+ ChangeNotifier 实现该应用程序 - flutter_bloc 和 Mobx,它们在其架构中使用
provider
从 4.x.x 迁移到 5.0.0-nullsafety
-
FutureProvider和StreamProvider的initialData现在是必需的。要迁移,以前的
FutureProvider<int>( create: (context) => Future.value(42), child: MyApp(), ) Widget build(BuildContext context) { final value = context.watch<int>(); return Text('$value'); }现在是
FutureProvider<int?>( initialValue: null, create: (context) => Future.value(42), child: MyApp(), ) Widget build(BuildContext context) { // be sure to specify the ? in watch<int?> final value = context.watch<int?>(); return Text('$value'); } -
ValueListenableProvider已删除要迁移,您可以使用
Provider结合ValueListenableBuilderValueListenableBuilder<int>( valueListenable: myValueListenable, builder: (context, value, _) { return Provider<int>.value( value: value, child: MyApp(), ); } )
用法
公开一个值
公开一个新的对象实例
Provider 不仅可以公开一个值,还可以创建、监听和处理它。
要公开新创建的对象,请使用 provider 的默认构造函数。
如果您想创建一个对象,请不要使用 .value 构造函数,否则您
可能会产生不期望的副作用。
请参阅 此 StackOverflow 回答
该回答解释了为什么使用 .value 构造函数创建值是不期望的。
- DO 在
create中创建一个新对象。
Provider(
create: (_) => MyModel(),
child: ...
)
- DON'T 使用
Provider.value来创建您的对象。
ChangeNotifierProvider.value(
value: MyModel(),
child: ...
)
-
DON'T 从可能随时间改变的变量创建您的对象。
在这种情况下,当
值更改时,您的对象将永远不会更新。
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 方法。
- DO 使用
ChangeNotifierProvider.value来提供现有的
ChangeNotifier.
MyChangeNotifier variable;
ChangeNotifierProvider.value(
value: variable,
child: ...
)
- DON'T 使用默认构造函数重用现有的 ChangeNotifier
MyChangeNotifier variable;
ChangeNotifierProvider(
create: (_) => variable,
child: ...
)
读取一个值
读取值的最简单方法是使用 [BuildContext] 上的扩展方法
context.watch<T>(),它使小部件监听T的更改context.read<T>(),它在不监听的情况下返回Tcontext.select<T, R>(R cb(T value)),它允许小部件仅监听T的一小部分。
或者使用静态方法 Provider.of<T>(context),它的行为类似watch,当您将 false 作为 listen 参数传递时,例如 Provider.of<T>(context, listen: false)
它的行为类似 read。
值得注意的是,context.read<T>() 不会使小部件在值
更改时重建,也不能在 StatelessWidget.build/State.build 中调用。
另一方面,可以在这些方法外部自由调用它。
这些方法将从与传递的 BuildContext 相关联的小部件开始向上遍历小部件树
并返回找到的最近的 T 类型的变量
(如果找不到则抛出)。
值得注意的是,此操作是 O(1) 的。它不涉及遍历
小部件树。
结合 公开一个值 的第一个示例,这
个小部件将读取公开的 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 的后代 BuildContext 很有用。
请参阅 FAQ
或 Consumer 的文档
和 Selector
了解更多信息。
可选地依赖于一个 provider
有时,我们可能希望支持 provider 不存在的情况。一个
例子是可重用的组件,它们可以在各种位置使用,
包括 provider 之外。
为此,在调用 context.watch/context.read 时,将泛型类型
设为可空。例如,不是
context.watch<Model>()
如果找不到匹配的 provider,将抛出 ProviderNotFoundException,请执行
这样
context.watch<Model?>()
将尝试获取匹配的 provider。但如果找不到,
将返回 null 而不是抛出。
MultiProvider
在大型应用程序中注入许多值时,Provider 可能会迅速变得
相当嵌套
Provider<Something>(
create: (_) => Something(),
child: Provider<SomethingElse>(
create: (_) => SomethingElse(),
child: Provider<AnotherThing>(
create: (_) => AnotherThing(),
child: someWidget,
),
),
),
到
MultiProvider(
providers: [
Provider<Something>(create: (_) => Something()),
Provider<SomethingElse>(create: (_) => SomethingElse()),
Provider<AnotherThing>(create: (_) => AnotherThing()),
],
child: someWidget,
)
两个示例的行为严格相同。MultiProvider 只改变
代码的外观。
ProxyProvider
自 3.0.0 起,出现了一种新的 provider:ProxyProvider。
ProxyProvider 是一个 provider,它将来自其他 provider 的多个值合并为一个新对象,并将结果发送到 Provider。
这个新对象将在其依赖的 provider 更新时进行更新。
以下示例使用 ProxyProvider 根据来自另一个 provider 的计数器来构建翻译。
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';
}
它有多种变体,例如
-
ProxyProvidervsProxyProvider2vsProxyProvider3, ...类名后的数字是其他 provider 的数量
ProxyProvider依赖于。 -
ProxyProvidervsChangeNotifierProxyProvidervsListenableProxyProvider, ...它们都以类似的方式工作,但不是将结果发送到
Provider,ChangeNotifierProxyProvider将其值发送到ChangeNotifierProvider。
常见问题
我能检查我的对象的内容吗?
Flutter 附带了一个 开发工具,该工具显示
在给定时刻的 widget 树。
由于 provider 是 widget,因此它们在该开发工具中也可见
从那里,如果您点击一个 provider,您将能够看到它公开的值
(使用 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 中获取 Provider 时遇到异常。我该怎么办?
此异常发生是因为您试图从一个
生命周期中监听 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,并且在更新它时遇到异常。发生了什么?
这很可能是因为您在小部件树构建期间,从其子项修改了 ChangeNotifier。
通常发生这种情况的一个典型情况是启动 http 请求,其中 future 存储在 notifier 中
initState() {
super.initState();
context.read<MyNotifier>().fetchSomething();
}
这是不允许的,因为状态更新是同步的。
这意味着某些小部件可能在变异发生之前构建(获得旧值),而其他小部件将在变异完成后构建(获得新值)。这可能导致 UI 不一致,因此不允许。
相反,您应该在影响
整个树同等的部分执行该变异
-
直接在 provider 的
create或模型的构造函数中class MyNotifier with ChangeNotifier { MyNotifier() { _fetchSomething(); } Future<void> _fetchSomething() async {} }当没有“外部参数”时,这很有用。
-
异步地在帧结束时
initState() { super.initState(); Future.microtask(() => context.read<MyNotifier>().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.watch,您可以使用 context.select 来仅监听所获得对象上特定属性的集合。
例如,虽然您可以这样写
Widget build(BuildContext context) {
final person = context.watch<Person>();
return Text(person.name);
}
如果除了 name 之外的其他内容发生更改,可能会导致 widget 重建。
相反,您可以使用 context.select 来仅监听 name 属性
Widget build(BuildContext context) {
final name = context.select((Person p) => p.name);
return Text(name);
}
这样,如果 name 之外的其他内容发生更改,widget 将不会不必要地重建。
同样,您可以使用 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: ...,
),
),
我能消费一个接口并提供一个实现吗?
是的,必须向编译器提供类型提示,以指示接口将被消耗,并在 create 中提供实现。
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 | 最基本的 provider 形式。它接受一个值并公开它,无论该值是什么。 |
| ListenableProvider | Listenable 对象的特定 provider。ListenableProvider 将监听该对象,并在每次调用监听器时要求依赖它的 widget 重建。 |
| ChangeNotifierProvider | ChangeNotifier 的 ListenableProvider 的一个规范。它将在需要时自动调用 ChangeNotifier.dispose。 |
| ValueListenableProvider | 监听 ValueListenable 并仅公开 ValueListenable.value。 |
| StreamProvider | 监听 Stream 并公开发出的最新值。 |
| FutureProvider | 接受一个 Future 并在 future 完成时更新依赖项。 |