provider

一个 InheritedWidget 的包装器,使其更易于使用和重用。

通过使用 provider 而不是手动编写 InheritedWidget,您可以获得

  • 简化的资源分配/处理
  • 懒加载
  • 每次创建新类时的样板代码大幅减少
  • 对开发工具友好
  • 一种消费这些 InheritedWidget 的通用方法(请参阅 Provider.of/Consumer/Selector
  • 增加了具有指数级增长的监听机制的类的可扩展性
    在复杂性方面(例如 ChangeNotifier,其通知分发是 O(N) 的)。

要了解有关 provider 的更多信息,请参阅其 文档

另请参阅

从 4.x.x 迁移到 5.0.0-nullsafety

  • FutureProviderStreamProviderinitialData 现在是必需的。

    要迁移,以前的

    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 结合 ValueListenableBuilder

    ValueListenableBuilder<int>(
      valueListenable: myValueListenable,
      builder: (context, value, _) {
        return Provider<int>.value(
          value: value,
          child: MyApp(),
        );
      }
    )
    

用法

公开一个值

公开一个新的对象实例

Provider 不仅可以公开一个值,还可以创建、监听和处理它。

要公开新创建的对象,请使用 provider 的默认构造函数。
如果您想创建一个对象,请不要使用 .value 构造函数,否则您
可能会产生不期望的副作用。

请参阅 此 StackOverflow 回答
该回答解释了为什么使用 .value 构造函数创建值是不期望的。

  • DOcreate 中创建一个新对象。
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: ...
)
MyChangeNotifier variable;

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

读取一个值

读取值的最简单方法是使用 [BuildContext] 上的扩展方法

  • context.watch<T>(),它使小部件监听 T 的更改
  • context.read<T>(),它在不监听的情况下返回 T
  • context.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>(),
    );
  }
}

或者,除了使用这些方法之外,我们还可以使用 ConsumerSelector

它们对于性能优化或难以
获取 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';
}

它有多种变体,例如

  • ProxyProvider vs ProxyProvider2 vs ProxyProvider3, ...

    类名后的数字是其他 provider 的数量
    ProxyProvider 依赖于。

  • ProxyProvider vs ChangeNotifierProxyProvider vs ListenableProxyProvider, ...

    它们都以类似的方式工作,但不是将结果发送到 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 更新时重建。FooBaz 不会
不必要地重建。

我能获取两个不同类型的 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 完成时更新依赖项。

GitHub

https://github.com/rrousselGit/provider