原文在这里。

尽管官网Flutter站点(状态治理入门app)说Provider“非常容易了解”,我(作者)可不这么认为。我想是因为Provider的品种有点多:

  • Provider
  • ListenableProvider
  • ChangeNotifierProvider
  • ValueListenableProvider
  • StreamProvider
  • FutureProvider
  • MultiProvider
  • ProxyProvider
  • ChangeNotifierProxyProvider
  • 更多

我只想用最简略的办法治理我的app的状态。为什么有这么多的抉择?我应该用哪一个呢?从哪里开始呢?

本文的目标就是帮你了解次要的Provider类型是怎么用的。我会给每一个Provide类型一个简略的例子。帮忙你了解,之后你就能够本人决定用哪个来治理你的app的状态了。

筹备

我的例子会应用这样的布局

也就是:

  • 那个“Do something”按钮代表扭转app状态的事件。
  • 那个“Show something”文本Widget代表显示app state的UI
  • 右边的绿色方框和左边的蓝色的方框代表了widget树的不同局部。用来强调一个事件和与这个事件所更改的状态相干的更新的UI。

这里是代码:

import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      home: Scaffold(        appBar: AppBar(title: Text('My App')),        body: Row(          mainAxisAlignment: MainAxisAlignment.center,          children: <Widget>[            Container(              padding: const EdgeInsets.all(20),              color: Colors.green[200],              child: RaisedButton(                child: Text('Do something'),                onPressed: () {},              ),            ),            Container(              padding: const EdgeInsets.all(35),              color: Colors.blue[200],              child: Text('Show something'),            ),          ],        ),      ),    );  }}

这些例子须要你装置Provider包

dependencies:  provider: ^4.0.1

而且曾经import到了须要的中央:

import 'package:provider/provider.dart';

Provider

如你所想,Provider是最根本的Provider类型。你能够用它来给widget树的任何中央提供一个值(个别是data model对象)。然而,值发生变化的时候是不会帮你更新widget树的。

假如你的app状态在一个model里:

class MyModel {   String someValue = 'Hello';  void doSomething() {    someValue = 'Goodbye';    print(someValue);  }}

你能够用Provider包装顶层Widget,给widget树提供数据。应用Consume widget来取得这个数据的援用。

你能够在西面的代码找到Provider和两个Consume widget:

import 'package:flutter/material.dart';import 'package:provider/provider.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Provider<MyModel>( //                                <--- Provider      create: (context) => MyModel(),      child: MaterialApp(        home: Scaffold(          appBar: AppBar(title: Text('My App')),          body: Row(            mainAxisAlignment: MainAxisAlignment.center,            children: <Widget>[              Container(                padding: const EdgeInsets.all(20),                color: Colors.green[200],                child: Consumer<MyModel>( //                    <--- Consumer                  builder: (context, myModel, child) {                    return RaisedButton(                      child: Text('Do something'),                      onPressed: (){                        // We have access to the model.                        myModel.doSomething();                      },                    );                  },                )              ),              Container(                padding: const EdgeInsets.all(35),                color: Colors.blue[200],                child: Consumer<MyModel>( //                    <--- Consumer                  builder: (context, myModel, child) {                    return Text(myModel.someValue);                  },                ),              ),            ],          ),        ),      ),    );  }}class MyModel { //                                               <--- MyModel  String someValue = 'Hello';  void doSomething() {    someValue = 'Goodbye';    print(someValue);  }}

运行代码,你会看到这个后果:

NOTES:

  • UI上显示了mode对象的Hello
  • 点击“Do something”按钮会引发一个导致model数据扭转的事件。然而,即便model的数据扭转了,UI也不会产生更改。因为Provider不会监听model对象的数据变更。

ChangeNotifierProvider

与根本的Provider widget不同,ChangeNotifierProvider会监听model对象的变动。当数据产生变更,它会重绘Consumer的子widget。

在下面的代码里,把Provider换成ChangeNotifierProvider。Model类也须要应用ChangeNotifier mixin(or 扩大它)。这样你能够应用notifyListeners()办法。当你调用这个办法的时候,ChangeNotifierProvider就会收到告诉,Consumer widget就会重绘它的子组件了。

上面是残缺代码:

import 'package:flutter/material.dart';import 'package:provider/provider.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return ChangeNotifierProvider<MyModel>( //      <--- ChangeNotifierProvider      create: (context) => MyModel(),      child: MaterialApp(        home: Scaffold(          appBar: AppBar(title: Text('My App')),          body: Row(            mainAxisAlignment: MainAxisAlignment.center,            children: <Widget>[              Container(                  padding: const EdgeInsets.all(20),                  color: Colors.green[200],                  child: Consumer<MyModel>( //                  <--- Consumer                    builder: (context, myModel, child) {                      return RaisedButton(                        child: Text('Do something'),                        onPressed: (){                          myModel.doSomething();                        },                      );                    },                  )              ),              Container(                padding: const EdgeInsets.all(35),                color: Colors.blue[200],                child: Consumer<MyModel>( //                    <--- Consumer                  builder: (context, myModel, child) {                    return Text(myModel.someValue);                  },                ),              ),            ],          ),        ),      ),    );  }}class MyModel with ChangeNotifier { //                          <--- MyModel  String someValue = 'Hello';  void doSomething() {    someValue = 'Goodbye';    print(someValue);    notifyListeners();  }}

当初当你点击了“Do something”按钮,文本会从“Hello”变成“Goodbye”。

NOTE:

  • 少数的app里,model类都在各自的文件里。你须要在这些文件里import flutter/foundation.dart,这样能力用ChangeNotifier。我不太喜爱这样,因为这样的话就表明你的业务逻辑里蕴含了一个framework的依赖,而且这个framework还是一个和逻辑无关的“细节”。咱们临时先这样。
  • notifyListeners()办法后,Consumer widget会重绘它的子树的所有widget都会重绘。按钮没有必要更新,所以绝对于Consumer你能够用Provider.of,并把listener设置为false。这样model有变动,按钮也不会更新了。这里是批改了之后的按钮widget。
class MyButton extends StatelessWidget {  @override  Widget build(BuildContext context) {    final myModel = Provider.of<MyModel>(context, listen: false);    return RaisedButton(      child: Text('Do something'),      onPressed: () {        myModel.doSomething();      },    );  }}

上面的代码还是老样子,应用Consumer组件。

FutureProvider

FutureProvider基本上是FutureBuilder的一个封装widget。你能够设置一些UI显示的初始数据,而后能够给一个Future值。FutureProvider会在Future实现的时候告诉Consumer重绘它的widget子树。

在上面的代码里我给UI提供了一个空的model。我也加了一个办法,会在3秒钟后返回一个新的model对象。这也是FutureProvider在期待的。

和根本的Provider一样,FutureProvider不会监听model自身的任何更改。在上面的代码里会表明这一点。

import 'package:flutter/material.dart';import 'package:provider/provider.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return FutureProvider<MyModel>( //                      <--- FutureProvider      initialData: MyModel(someValue: 'default value'),      create: (context) => someAsyncFunctionToGetMyModel(),      child: MaterialApp(        home: Scaffold(          appBar: AppBar(title: Text('My App')),          body: Row(            mainAxisAlignment: MainAxisAlignment.center,            children: <Widget>[              Container(                padding: const EdgeInsets.all(20),                color: Colors.green[200],                child: Consumer<MyModel>( //                    <--- Consumer                  builder: (context, myModel, child) {                    return RaisedButton(                      child: Text('Do something'),                      onPressed: (){                        myModel.doSomething();                      },                    );                  },                )              ),              Container(                padding: const EdgeInsets.all(35),                color: Colors.blue[200],                child: Consumer<MyModel>( //                    <--- Consumer                  builder: (context, myModel, child) {                    return Text(myModel.someValue);                  },                ),              ),            ],          ),        ),      ),    );      }}Future<MyModel> someAsyncFunctionToGetMyModel() async { //  <--- async function  await Future.delayed(Duration(seconds: 3));  return MyModel(someValue: 'new data');}class MyModel { //                                               <--- MyModel  MyModel({this.someValue});  String someValue = 'Hello';  Future<void> doSomething() async {    await Future.delayed(Duration(seconds: 2));    someValue = 'Goodbye';    print(someValue);  }}

NOTE:

  • FutureProvider会在Futuer<MyModel>实现后告诉Consumer重绘。
  • 点击Hot restart重置app
  • 留神,点击“Do something”按钮不会更新UI,即便是在Future实现了之后。如果你想让UI能够重绘,应用ChangeNotifierProvider
  • FutureProvider的应用状况个别是在解决网络或者文本读取数据的时候。然而,也能够应用FutureBuilder来达到同样的目标。以我(作者)不成熟的认识,FutureProvider没有比FutureBuilder更有用。如果我须要一个provider,我基本上会用ChangeNotifierProvider,如果我不须要provider的话可能会用FutureProvider

StreamProvider

StreamProviderStreamBuilder的一个封装。你提供一个stream,而后Consumer会在stream收到一个事件的时候更新它的widget子树。这和下面的FutureProvider十分类似。

你能够把stream发送的值当做不可批改的。也就是说,StreamProvider不会监听model的变动。只会监听stream里的新事件。

代码如下:

import 'package:flutter/material.dart';import 'package:provider/provider.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return StreamProvider<MyModel>( //                       <--- StreamProvider      initialData: MyModel(someValue: 'default value'),      create: (context) => getStreamOfMyModel(),      child: MaterialApp(        home: Scaffold(          appBar: AppBar(title: Text('My App')),          body: Row(            mainAxisAlignment: MainAxisAlignment.center,            children: <Widget>[              Container(                padding: const EdgeInsets.all(20),                color: Colors.green[200],                child: Consumer<MyModel>( //                    <--- Consumer                  builder: (context, myModel, child) {                    return RaisedButton(                      child: Text('Do something'),                      onPressed: (){                        myModel.doSomething();                      },                    );                  },                )              ),              Container(                padding: const EdgeInsets.all(35),                color: Colors.blue[200],                child: Consumer<MyModel>( //                    <--- Consumer                  builder: (context, myModel, child) {                    return Text(myModel.someValue);                  },                ),              ),            ],          ),        ),      ),    );      }}Stream<MyModel> getStreamOfMyModel() { //                        <--- Stream  return Stream<MyModel>.periodic(Duration(seconds: 1),          (x) => MyModel(someValue: '$x'))      .take(10);}class MyModel { //                                               <--- MyModel  MyModel({this.someValue});  String someValue = 'Hello';  void doSomething() {    someValue = 'Goodbye';    print(someValue);  }}

NOTE:

  • StreamProvider在收到新的事件之后告诉Consumer重绘
  • 应用hot restart重置app
  • 点击“Do something”不会重绘UI。如果你想要UI能够更新,那么应用ChangeNotifierProvider。事实上,你能够在model里加一个stream,而后调用notifyListeners()。这样的话齐全不须要StreamProvider了。
  • 你能够应用StreamProvider实现BLoC模式

ValueListenableProvider

你能够略过这一节,它和ChangeNotifierProvider根本一样,只是更加简单一点,而且没有什么额定的益处。。。

如果你有一个这样的ValueNotifier

class MyModel {  ValueNotifier<String> someValue = ValueNotifier('Hello');  void doSomething() {    someValue.value = 'Goodbye';  }}

那么你能够用ValueListenableProvider来监听这个值的变动。然而,如果你想在UI外面调用model的办法,那么你也须要在provider里provide这个model对象。因而,你会在上面的代码里看到一个Provider提供了一个MyModel对象给Consumer,而这个provider同时也给了ValueListenableProvider它须要的蕴含在MyModelValueNotifier属性someValue。也就是如果只是监听一个model对象的属性值只须要ValueListenableProvider,然而你想在UI里调用这个model的办法,那么还要额定写一个Provider

残缺代码如下:

import 'package:flutter/material.dart';import 'package:provider/provider.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Provider<MyModel>(//                              <--- Provider      create: (context) => MyModel(),      child: Consumer<MyModel>( //                           <--- MyModel Consumer          builder: (context, myModel, child) {            return ValueListenableProvider<String>.value( // <--- ValueListenableProvider              value: myModel.someValue,              child: MaterialApp(                home: Scaffold(                  appBar: AppBar(title: Text('My App')),                  body: Row(                    mainAxisAlignment: MainAxisAlignment.center,                    children: <Widget>[                      Container(                          padding: const EdgeInsets.all(20),                          color: Colors.green[200],                          child: Consumer<MyModel>( //       <--- Consumer                            builder: (context, myModel, child) {                              return RaisedButton(                                child: Text('Do something'),                                onPressed: (){                                  myModel.doSomething();                                },                              );                            },                          )                      ),                      Container(                        padding: const EdgeInsets.all(35),                        color: Colors.blue[200],                        child: Consumer<String>(//           <--- String Consumer                          builder: (context, myValue, child) {                            return Text(myValue);                          },                        ),                      ),                    ],                  ),                ),              ),            );          }),    );  }}class MyModel { //                                             <--- MyModel  ValueNotifier<String> someValue = ValueNotifier('Hello'); // <--- ValueNotifier  void doSomething() {    someValue.value = 'Goodbye';    print(someValue.value);  }}

不过作者自己对以上的认识起初产生了扭转,他举荐大家看这篇文章。

ListenableProvider

如果你要定制一个provider,那么就须要用到这个provider类型。不过文档还是揭示,你兴许只是须要一个ChangeNotifierProvider。所以,这个内容临时疏忽。之后我(作者)会更新这部分。

MultiProvider

到目前为止,咱们的例子还是只用过一个model对象。如果你须要另外的一个model对象,你就要嵌套provider了(就和在解说ValueListenableProvider的时候一样)。然而,嵌套只会造成凌乱。一个更好的方法就是应用MultiProvider

在上面的例子里会应用ChangeNotifierProvider提供两种model对象。

上面是全副代码。有点长。只须要留神MultiProviderConsumer和两个model类。

import 'package:flutter/material.dart';import 'package:provider/provider.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MultiProvider( //                                     <--- MultiProvider      providers: [        ChangeNotifierProvider<MyModel>(create: (context) => MyModel()),        ChangeNotifierProvider<AnotherModel>(create: (context) => AnotherModel()),      ],      child: MaterialApp(        home: Scaffold(          appBar: AppBar(title: Text('My App')),          body: Column(            children: <Widget>[              Row(                mainAxisAlignment: MainAxisAlignment.center,                children: <Widget>[                  Container(                      padding: const EdgeInsets.all(20),                      color: Colors.green[200],                      child: Consumer<MyModel>( //            <--- MyModel Consumer                        builder: (context, myModel, child) {                          return RaisedButton(                            child: Text('Do something'),                            onPressed: (){                              // We have access to the model.                              myModel.doSomething();                            },                          );                        },                      )                  ),                  Container(                    padding: const EdgeInsets.all(35),                    color: Colors.blue[200],                    child: Consumer<MyModel>( //              <--- MyModel Consumer                      builder: (context, myModel, child) {                        return Text(myModel.someValue);                      },                    ),                  ),                ],              ),             // SizedBox(height: 5),              Row(                mainAxisAlignment: MainAxisAlignment.center,                children: <Widget>[                  Container(                      padding: const EdgeInsets.all(20),                      color: Colors.red[200],                      child: Consumer<AnotherModel>( //      <--- AnotherModel Consumer                        builder: (context, myModel, child) {                          return RaisedButton(                            child: Text('Do something'),                            onPressed: (){                              myModel.doSomething();                            },                          );                        },                      )                  ),                  Container(                    padding: const EdgeInsets.all(35),                    color: Colors.yellow[200],                    child: Consumer<AnotherModel>( //        <--- AnotherModel Consumer                      builder: (context, anotherModel, child) {                        return Text('${anotherModel.someValue}');                      },                    ),                  ),                ],              ),            ],          ),        ),      ),    );  }}class MyModel with ChangeNotifier { //                        <--- MyModel  String someValue = 'Hello';  void doSomething() {    someValue = 'Goodbye';    print(someValue);    notifyListeners();  }}class AnotherModel with ChangeNotifier { //                   <--- AnotherModel  int someValue = 0;  void doSomething() {    someValue = 5;    print(someValue);    notifyListeners();  }}

NOTES:

  • 点击第一个“Do something”按钮,文本会从Hello变成Goodbye。点击第二个“Do something”按钮,文本会从0变成5
  • 这和单个的ChangeNotifierProvider没有很大的不同。Consumer通过不同的类型参数获取到对应的model对象。

ProxyProvider

如果你有两个model对象要提供,而且这两个model对象之间还有依赖关系。在这个状况下你能够应用ProxyProvider。一个ProxyProvider从一个provider里承受对象,而后把这个对象注入另外一个provider。

一开始你会对ProxyProvider的应用办法有些困惑,这个例子能够帮你加深了解。

MultiProvider(  providers: [    ChangeNotifierProvider<MyModel>(      create: (context) => MyModel(),    ),    ProxyProvider<MyModel, AnotherModel>(      update: (context, myModel, anotherModel) => AnotherModel(myModel),    ),  ],

根本的ProxyProvider有两个类型参数。第二个依赖于第一个。在ProxyProviderudpate办法里,第三个参数有anotherModel存了上次的值,然而咱们不在这里应用。咱们只是把myModel当做参数传入AnotherModel的构造函数里。

残缺代码:

import 'package:flutter/material.dart';import 'package:provider/provider.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MultiProvider( //                              <--- MultiProvider      providers: [        ChangeNotifierProvider<MyModel>( //               <--- ChangeNotifierProvider          create: (context) => MyModel(),        ),        ProxyProvider<MyModel, AnotherModel>( //          <--- ProxyProvider          update: (context, myModel, anotherModel) => AnotherModel(myModel),        ),      ],      child: MaterialApp(        home: Scaffold(          appBar: AppBar(title: Text('My App')),          body: Column(            children: <Widget>[              Row(                mainAxisAlignment: MainAxisAlignment.center,                children: <Widget>[                  Container(                      padding: const EdgeInsets.all(20),                      color: Colors.green[200],                      child: Consumer<MyModel>( //          <--- MyModel Consumer                        builder: (context, myModel, child) {                          return RaisedButton(                            child: Text('Do something'),                            onPressed: (){                              myModel.doSomething('Goodbye');                            },                          );                        },                      )                  ),                  Container(                    padding: const EdgeInsets.all(35),                    color: Colors.blue[200],                    child: Consumer<MyModel>( //            <--- MyModel Consumer                      builder: (context, myModel, child) {                        return Text(myModel.someValue);                      },                    ),                  ),                ],              ),              Container(                  padding: const EdgeInsets.all(20),                  color: Colors.red[200],                  child: Consumer<AnotherModel>( //          <--- AnotherModel Consumer                    builder: (context, anotherModel, child) {                      return RaisedButton(                        child: Text('Do something else'),                        onPressed: (){                          anotherModel.doSomethingElse();                        },                      );                    },                  )              ),            ],          ),        ),      ),    );  }}class MyModel with ChangeNotifier { //                       <--- MyModel  String someValue = 'Hello';  void doSomething(String value) {    someValue = value;    print(someValue);    notifyListeners();  }}class AnotherModel { //                                      <--- AnotherModel  MyModel _myModel;  AnotherModel(this._myModel);  void doSomethingElse() {    _myModel.doSomething('See you later');    print('doing something else');  }}

NOTES:

  • 开始文本显示的是Hello
  • 当你点击“Do something”按钮,MyModel对象的文本会变成GoodbyeMyModel告诉了ChangeNotifierProvider, UI也显示了新的文本。
  • 当你点击“Do something else”按钮,AnotherModel接管MyModelProxyProvider注入),之后批改文本为"See you later"。因为MyModel产生更改的时候告诉了listener,所以UI产生了更新。如果AnotherModel更改了它的值,UI是不会更新的。因为ProxyProvider不会监听更改。这时候能够应用ChangeNotifierProxyProvider
  • ProxyProvider还是让我有点迷糊。ChangeNotifierProxyProvider自身就有更多的更多阐明了,所以我不打算介绍更多了。
  • 我(作者)FilledStack用GetIt比用ProxyProvider实现依赖注入更好用。

Provider builder和value constructor

最初还有一件事须要解释。

大部分的provider都有两种构造函数。一个根本的接管一个create办法,用来新建你的model对象。咱们在下面的例子中根本都是这么做的:

Provider<MyModel>(  create: (context) => MyModel(),  child: ...)

能够看到MyModel对象是在create办法创立的。

如果你的对象曾经创立好了,你只是想要提供一个它的援用。那么你能够用命名构造方法value:

final myModel = MyModel();...Provider<MyModel>.value(    value: myModel,     child: ...)

这里MyModel对象提前就建好了,只提供了一个援用。如果你在initState办法曾经建好了对象,你就能够这么做。

总结

基本上你能够疏忽provider包里的大多数类。只记住怎么应用ChangeNotifierProviderConsumer。如果你不想要更新UI,那么就应用Provider。应用Future和Stream的场景都能够把他们放在你的Model类外面,而后告诉ChangeNotifierProvider。不须要FutureProviderStreamProvdier。大多数的时候不须要MultiProvider。如果有依赖注入的时候,也能够用GetIt包来实现,不须要用ProxyProvider。这篇文章有更深刻的介绍。

下一步: Riverpod

你兴许据说过Riverpod,这是一个构建在provider之上的状态治理库。作者也是同一个作者。如果你对provider的应用还有困惑,那么riverpod只会让你更加困惑。然而,一旦相熟了这个库,那么它会让所有变得简略。我(作者)倡议你试一试。我写了一个Riverpod的教程心愿对你有帮忙。