Build Status
pub package codecov Gitter

依赖注入(DI)和状态管理之间的混合体,使用
小部件为小部件而构建。

它特意使用小部件进行DI/状态管理,而不是像Stream这样的纯Dart
类。
原因是,小部件非常简单,但又健壮且可扩展。

通过使用小部件进行状态管理,provider可以保证

  • 可维护性,通过强制的单向数据流
  • 可测试性/可组合性,因为总是可以模拟/覆盖一个
    value
  • 健壮性,因为很难忘记处理模型/小部件的更新场景
    模型/小部件

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

从v2.0.0迁移到v3.0.0

  • Providers现在不能再用const实例化了。
  • Provider现在如果与Listenable/Stream一起使用会抛出异常。
    请考虑改用ListenableProvider/StreamProvider。或者,
    可以通过将
    Provider.debugCheckInvalidValueType设置为null来禁用此异常,如下所示

void main() {
  Provider.debugCheckInvalidValueType = null;

  runApp(MyApp());
}
  • 所有XXProvider.value构造函数现在都使用value作为参数名。

之前

ChangeNotifierProvider.value(notifier: myNotifier),

之后

ChangeNotifierProvider.value(value: myNotifier),
  • StreamProvider的默认构造函数现在构建一个Stream而不是一个
    StreamController。之前的行为已移至命名
    构造函数StreamProvider.controller

之前

StreamProvider(builder: (_) => StreamController<int>()),

之后

StreamProvider.controller(builder: (_) => StreamController<int>()),

用法

暴露一个值

要通过provider暴露一个变量,请将任何小部件包装到该包中的一个提供程序
小部件中,并将变量传递给它。然后,所有子孙小部件
新添加的提供程序小部件都可以访问此变量。

一个简单的例子是将整个应用程序包装在一个Provider
小部件中并传递我们的变量

Provider<String>.value(
  value: 'Hello World',
  child: MaterialApp(
    home: Home(),
  )
)

或者,对于复杂对象,大多数提供程序都公开一个接受创建值的函数的构造函数。提供程序将在小部件插入到树中时仅调用该函数一次,并公开结果。这对于公开一个永不随时间变化而又无需编写StatefulWidget的复杂对象非常完美。
该提供程序将调用该函数仅
一次,当插入小部件在树中,并公开结果。这是
完美的暴露一个复杂的对象,永不随时间变化,而无需
编写StatefulWidget

以下内容创建并公开了一个MyComplexClass。并且在Provider被移除出小部件树的情况下,实例化的MyComplexClass将被处置。
Provider被移除
将被处置。

Provider<MyComplexClass>(
  builder: (context) => MyComplexClass(),
  dispose: (context, value) => value.dispose()
  child: SomeWidget(),
)

读取值

读取值的最简单方法是使用静态方法
Provider.of<T>(BuildContext context).

此方法将从与传递的BuildContext相关联的小部件开始在小部件树中查找,并返回找到的类型为T的最近变量(如果找不到则抛出异常)。
从该小部件开始
T找到(或抛出,如果找不到)。

结合暴露值的第一个示例,这个
小部件将读取暴露的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 `Provider.of`!
      Provider.of<String>(context)
    );
  }
}

或者,除了使用Provider.of之外,我们还可以使用ConsumerSelector

这些对于性能优化或难以获取提供程序子孙BuildContext的情况可能很有用。
获取BuildContext的子孙。

请参阅FAQConsumer
Selector的文档
了解更多信息。

MultiProvider

在大型应用程序中注入多个值时,Provider可能会变得相当
嵌套

Provider<Foo>.value(
  value: foo,
  child: Provider<Bar>.value(
    value: bar,
    child: Provider<Baz>.value(
      value: baz,
      child: someWidget,
    )
  )
)

在这种情况下,我们可以使用MultiProvider来提高可读性

MultiProvider(
  providers: [
    Provider<Foo>.value(value: foo),
    Provider<Bar>.value(value: bar),
    Provider<Baz>.value(value: baz),
  ],
  child: someWidget,
)

两个示例的行为严格相同。MultiProvider仅改变
代码的外观。

ProxyProvider

从3.0.0开始,有一个新的提供程序类型:ProxyProvider

ProxyProvider是一个提供程序,它将其他提供程序中的多个值组合成一个新对象,并将结果发送给Provider
与另一个提供程序相结合

那个新对象将在它所依赖的任何提供程序更新时进行更新。
更新。

下面的示例使用ProxyProvider根据来自另一个提供程序的计数器构建翻译。
计数器

Widget build(BuildContext context) {
  return MultiProvider(
    providers: [
      ChangeNotifierProvider(builder: (_) => Counter()),
      ProxyProvider<Counter, Translations>(
        builder: (_, 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,等等。

    类名后面的数字是它所依赖的其他提供程序的数量
    ProxyProvider依赖。

  • ProxyProvider vs ChangeNotifierProxyProvider vs ListenableProxyProvider,等等。

    它们的工作方式相似,但不是将结果发送到Provider
    ChangeNotifierProxyProvider会将值发送到ChangeNotifierProvider

常见问题

initState中获取Providers时出现异常。我该怎么办?

此异常发生是因为您正试图从一个生命周期中监听一个提供程序,该生命周期永远不会再次调用。
生命周期

这意味着您应该使用另一个生命周期
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);
  }
}

它会在value更改时打印value

或者您可以这样做

initState() {
  super.initState();
  print(Provider.of<Foo>(context, listen: false).value);
}

它将只打印一次value并忽略更新。

我使用ChangeNotifier,并在更新它时遇到异常,发生了什么?

这很可能是因为您正在修改ChangeNotifier,而此时它位于其子孙之中在小部件树构建过程中
这种情况的一个典型例子是在启动http请求时,其中

将来被存储在通知器中
通知器

initState() {
  super.initState();
  Provider.of<Foo>(context).fetchSomething();
}

这是不允许的,因为修改是即时的。

这意味着一些小部件可能在突变之前构建,而另一些
小部件将在突变之后构建。
这可能导致UI不一致,因此不允许。

相反,您应该在会同等影响整个树的地方执行该突变
整个树

  • 直接在提供程序/模型构造函数的builder

    class MyNotifier with ChangeNotifier {
      MyNotifier() {
        _fetchSomething();
      }
    
      Future<void> _fetchSomething() async {}
    }

    当没有“外部参数”时,这很有用。

  • 异步地,在帧的末尾

    initState() {
      super.initState();
      Future.microtask(() =>
        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(
  onPressed: Provider.of<ExampleState>(context).increment,
  child: Icon(Icons.plus_one),
);

或者,您可以创建自己的提供程序。

我可以创建自己的Provider吗?

是的。provider公开了构成一个功能齐全的提供程序的所有小型组件。

这包括

  • SingleChildCloneableWidget,用于使任何小部件与MultiProvider一起工作。
  • InheritedProvider,在执行Provider.of时获取的通用InheritedWidget
  • DelegateWidget/BuilderDelegate/ValueDelegate,用于处理
    “MyProvider()创建对象”与“MyProvider.value()可以随时间更新”的逻辑。

这是一个用于使用ValueNotifier作为状态的自定义提供程序的示例
https://gist.github.com/rrousselGit/4910f3125e41600df3c2577e26967c91

我的小部件重建得太频繁了,我该怎么办?

使用Consumer/Selector代替Provider.of

它们可选的child参数允许只重建非常特定的小部件部分

Foo(
  child: Consumer<A>(
    builder: (_, a, child) {
      return Bar(a: a, child: child);
    },
    child: Baz(),
  ),
)

在此示例中,只有Bar会在A更新时重建。FooBaz不会
不必要地重建。

更进一步,可以使用Selector来忽略更改,如果
它们不对小部件树产生影响

Selector<List, int>(
  selector: (_, list) => list.length,
  builder: (_, length, __) {
    return Text('$length');
  }
);

此片段仅在列表长度更改时重建。但它不会
在更新项时无意中更新。

我能获取同一类型的两个不同提供程序吗?

不能。虽然您可以拥有多个共享相同类型的提供程序,但一个小部件将只能获取其中一个:最近的祖先。
最近的祖先。

相反,您必须明确地为两个提供程序指定不同的类型。

而不是

Provider<String>(
  builder: (_) => 'England',
  child: Provider<String>(
    builder: (_) => 'London',
    child: ...,
  ),
),

首选

Provider<Country>(
  builder: (_) => Country('England'),
  child: Provider<City>(
    builder: (_) => City('London'),
    child: ...,
  ),
),

现有提供程序

provider公开了几种不同类型的“提供程序”,用于不同类型的对象。

所有可用对象的完整列表在此处:here

名称 描述
Provider 最基本的提供程序形式。它接收一个值并公开它,无论该值是什么。
ListenableProvider Listenable对象的特定提供程序。ListenableProvider将监听该对象,并在调用侦听器时要求依赖它的那些小部件重建。
ChangeNotifierProvider ChangeNotifier的ListenableProvider的规范。它将在需要时自动调用ChangeNotifier.dispose
ValueListenableProvider 监听一个ValueListenable并仅公开ValueListenable.value
StreamProvider 监听一个Stream并公开最新发出的值。
FutureProvider 接收一个Future并在Future完成时更新依赖项。

GitHub

查看 Github