类关系

Provider常用的类有:

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

我们阅读源码,绘出下面的UML类关系图,读者朋友们如果看不清楚,可以尝试将放大下浏览器视图百分比。

Provider用法一览 - 图1

例子

看到这么多类,我们就疯掉了:什么情况下用什么类?

以下内容理解来自于博客:https://medium.com/flutter-community/making-sense-all-of-those-flutter-providers-e842e18f45dd,这篇文章对于初次了解Provider的学习者来说,非常容易入门,强烈推荐读者阅读。

1. 建立项目

setup.png
我们先建立项目,演示很简单,有两个Container,左边Container中有个按钮 Do something ,右边Container中有个文本,目前是 Show something 。后面将演示,点击左边按钮,右边文本同步发生变化的同步机制。

先看下初始的代码。

  1. import 'package:flutter/material.dart';
  2. void main() => runApp(MyApp());
  3. class MyApp extends StatelessWidget {
  4. @override
  5. Widget build(BuildContext context) {
  6. return MaterialApp(
  7. home: Scaffold(
  8. appBar: AppBar(title: Text('My App')),
  9. body: Row(
  10. mainAxisAlignment: MainAxisAlignment.center,
  11. children: <Widget>[
  12. Container(
  13. padding: const EdgeInsets.all(20),
  14. color: Colors.green[200],
  15. child: RaisedButton(
  16. child: Text('Do something'),
  17. onPressed: () {},
  18. ),
  19. ),
  20. Container(
  21. padding: const EdgeInsets.all(35),
  22. color: Colors.blue[200],
  23. child: Text('Show something'),
  24. ),
  25. ],
  26. ),
  27. ),
  28. );
  29. }
  30. }

2. 引入Provider

pubspec.yaml 文件中引入 Provider 库:

  1. dependencies:
  2. provider: ^4.0.1

类中使用 Provider 的时候,引入类:

  1. import 'package:provider/provider.dart';

3. 使用Provider

Provider 顾名思义,是一个提供者,或者说提供器,它可以在 widget 树的任何地方提供一个 value ,通常是一个数据 model 。然而,这个基本的 Providervalue 发生变化的时候并不会更新 widget 树。

我们定义一个基本的 model

  1. class MyModel {
  2. String someValue = 'Hello';
  3. void doSomething() {
  4. someValue = 'Goodbye';
  5. print(someValue);
  6. }
  7. }

model 定义好了,如何使用 Provider 来将它提供出去呢?

1)在 wiget 树的某处(这里demo中选择了最顶层),使用 ProviderProvider 内部有个 create 函数,这里我们返回 MyModel 实例。
2)在需要使用 MyModel 实例的 widget 中,使用 Consumer 进行包裹一层, Consumer消费者的含义,意思是表达对 MyModel 实例的关注以及消费使用。

  1. import 'package:flutter/material.dart';
  2. import 'package:provider/provider.dart';
  3. void main() => runApp(MyApp());
  4. class MyApp extends StatelessWidget {
  5. @override
  6. Widget build(BuildContext context) {
  7. return Provider<MyModel>( // <--- Provider
  8. create: (context) => MyModel(),
  9. child: MaterialApp(
  10. home: Scaffold(
  11. appBar: AppBar(title: Text('My App')),
  12. body: Row(
  13. mainAxisAlignment: MainAxisAlignment.center,
  14. children: <Widget>[
  15. Container(
  16. padding: const EdgeInsets.all(20),
  17. color: Colors.green[200],
  18. child: Consumer<MyModel>( // <--- Consumer
  19. builder: (context, myModel, child) {
  20. return RaisedButton(
  21. child: Text('Do something'),
  22. onPressed: (){
  23. // We have access to the model.
  24. myModel.doSomething();
  25. },
  26. );
  27. },
  28. )
  29. ),
  30. Container(
  31. padding: const EdgeInsets.all(35),
  32. color: Colors.blue[200],
  33. child: Consumer<MyModel>( // <--- Consumer
  34. builder: (context, myModel, child) {
  35. return Text(myModel.someValue);
  36. },
  37. ),
  38. ),
  39. ],
  40. ),
  41. ),
  42. ),
  43. );
  44. }
  45. }
  46. class MyModel { // <--- MyModel
  47. String someValue = 'Hello';
  48. void doSomething() {
  49. someValue = 'Goodbye';
  50. print(someValue);
  51. }
  52. }

运行一下:
provider.png

:::info

  • 右侧文本”Hello”来自MyModel的someValue
  • 点击左侧按钮将会执行model的doSomething方法。doSomething虽然改变了model的someValue值,但是右侧文本并不会发生变化,因为Provider widget并没有监听它提供的值的变化。 :::

    4. 使用ChangeNotifierProvider

Provider widget不同,ChangeNotifierProvider会监听模型的变化。当模型发生变化时, Consumer 下的 widgets 都会被重建。

下面代码中,我们把 Provider 修改成了ChangeNotifierProviderMyModel 类需要继承 ChangeNotifier 或者使用 mixin 。这样我们就可以在模型发生变化的时候调用 notifyListeners() ,然后 ChangeNotifierProvider 就会被通知到然后 Consumerwidgets 就会被重建。

  1. import 'package:flutter/material.dart';
  2. import 'package:provider/provider.dart';
  3. void main() => runApp(MyApp());
  4. class MyApp extends StatelessWidget {
  5. @override
  6. Widget build(BuildContext context) {
  7. return ChangeNotifierProvider<MyModel>( // <--- ChangeNotifierProvider
  8. create: (context) => MyModel(),
  9. child: MaterialApp(
  10. home: Scaffold(
  11. appBar: AppBar(title: Text('My App')),
  12. body: Row(
  13. mainAxisAlignment: MainAxisAlignment.center,
  14. children: <Widget>[
  15. Container(
  16. padding: const EdgeInsets.all(20),
  17. color: Colors.green[200],
  18. child: Consumer<MyModel>( // <--- Consumer
  19. builder: (context, myModel, child) {
  20. return RaisedButton(
  21. child: Text('Do something'),
  22. onPressed: (){
  23. myModel.doSomething();
  24. },
  25. );
  26. },
  27. )
  28. ),
  29. Container(
  30. padding: const EdgeInsets.all(35),
  31. color: Colors.blue[200],
  32. child: Consumer<MyModel>( // <--- Consumer
  33. builder: (context, myModel, child) {
  34. return Text(myModel.someValue);
  35. },
  36. ),
  37. ),
  38. ],
  39. ),
  40. ),
  41. ),
  42. );
  43. }
  44. }
  45. class MyModel with ChangeNotifier { // <--- MyModel
  46. String someValue = 'Hello';
  47. void doSomething() {
  48. someValue = 'Goodbye';
  49. print(someValue);
  50. notifyListeners();
  51. }
  52. }

ChangeNotifierProvider.gif

:::info

  • 我们看下上面的代码,可以发现按钮的点击事件因为用了MyModel实例的doSomething方法,也作为了Consumer使用。但MyModel模型发生变化的时候,按钮是没有必要更新的。所以可以使用 Provider.of,并且设置listener为false。这样当模型变化时按钮就不会重建。下面是如何使用Provider.of。 :::
    1. class MyButton extends StatelessWidget {
    2. @override
    3. Widget build(BuildContext context) {
    4. final myModel = Provider.of<MyModel>(context, listen: false);
    5. return RaisedButton(
    6. child: Text('Do something'),
    7. onPressed: () {
    8. myModel.doSomething();
    9. },
    10. );
    11. }
    12. }

    5. 使用FutureProvider

FutureProvider 是对 FutureBuilder widget的封装。我们可以提供给一些初始化数据来展示UI,并且可以提供一个Future对象。 FutureProvider 监听到Future完成后,便通知 Consumers widgets进行重建。

下面代码展示了右侧文本初始以 ‘default value’ 展现,然后3秒后Future完成,返回’new data’展示。

  1. import 'package:flutter/material.dart';
  2. import 'package:provider/provider.dart';
  3. void main() => runApp(MyApp());
  4. class MyApp extends StatelessWidget {
  5. @override
  6. Widget build(BuildContext context) {
  7. return FutureProvider<MyModel>( // <--- FutureProvider
  8. initialData: MyModel(someValue: 'default value'),
  9. create: (context) => someAsyncFunctionToGetMyModel(),
  10. child: MaterialApp(
  11. home: Scaffold(
  12. appBar: AppBar(title: Text('My App')),
  13. body: Row(
  14. mainAxisAlignment: MainAxisAlignment.center,
  15. children: <Widget>[
  16. Container(
  17. padding: const EdgeInsets.all(20),
  18. color: Colors.green[200],
  19. child: Consumer<MyModel>( // <--- Consumer
  20. builder: (context, myModel, child) {
  21. return RaisedButton(
  22. child: Text('Do something'),
  23. onPressed: (){
  24. myModel.doSomething();
  25. },
  26. );
  27. },
  28. )
  29. ),
  30. Container(
  31. padding: const EdgeInsets.all(35),
  32. color: Colors.blue[200],
  33. child: Consumer<MyModel>( // <--- Consumer
  34. builder: (context, myModel, child) {
  35. return Text(myModel.someValue);
  36. },
  37. ),
  38. ),
  39. ],
  40. ),
  41. ),
  42. ),
  43. );
  44. }
  45. }
  46. Future<MyModel> someAsyncFunctionToGetMyModel() async { // <--- async function
  47. await Future.delayed(Duration(seconds: 3));
  48. return MyModel(someValue: 'new data');
  49. }
  50. class MyModel { // <--- MyModel
  51. MyModel({this.someValue});
  52. String someValue = 'Hello';
  53. Future<void> doSomething() async {
  54. await Future.delayed(Duration(seconds: 2));
  55. someValue = 'Goodbye';
  56. print(someValue);
  57. }
  58. }

FutureProvider.gif

6. 使用StreamProvider

  1. import 'package:flutter/material.dart';
  2. import 'package:provider/provider.dart';
  3. void main() => runApp(MyApp());
  4. class MyApp extends StatelessWidget {
  5. @override
  6. Widget build(BuildContext context) {
  7. return StreamProvider<MyModel>( // <--- StreamProvider
  8. initialData: MyModel(someValue: 'default value'),
  9. create: (context) => getStreamOfMyModel(),
  10. child: MaterialApp(
  11. home: Scaffold(
  12. appBar: AppBar(title: Text('My App')),
  13. body: Row(
  14. mainAxisAlignment: MainAxisAlignment.center,
  15. children: <Widget>[
  16. Container(
  17. padding: const EdgeInsets.all(20),
  18. color: Colors.green[200],
  19. child: Consumer<MyModel>( // <--- Consumer
  20. builder: (context, myModel, child) {
  21. return RaisedButton(
  22. child: Text('Do something'),
  23. onPressed: (){
  24. myModel.doSomething();
  25. },
  26. );
  27. },
  28. )
  29. ),
  30. Container(
  31. padding: const EdgeInsets.all(35),
  32. color: Colors.blue[200],
  33. child: Consumer<MyModel>( // <--- Consumer
  34. builder: (context, myModel, child) {
  35. return Text(myModel.someValue);
  36. },
  37. ),
  38. ),
  39. ],
  40. ),
  41. ),
  42. ),
  43. );
  44. }
  45. }
  46. Stream<MyModel> getStreamOfMyModel() { // <--- Stream
  47. return Stream<MyModel>.periodic(Duration(seconds: 1),
  48. (x) => MyModel(someValue: '$x'))
  49. .take(10);
  50. }
  51. class MyModel { // <--- MyModel
  52. MyModel({this.someValue});
  53. String someValue = 'Hello';
  54. void doSomething() {
  55. someValue = 'Goodbye';
  56. print(someValue);
  57. }
  58. }

StreamProvider.gif

7. 使用ValueListenableProvider

  1. import 'package:flutter/material.dart';
  2. import 'package:provider/provider.dart';
  3. void main() => runApp(MyApp());
  4. class MyApp extends StatelessWidget {
  5. @override
  6. Widget build(BuildContext context) {
  7. return Provider<MyModel>(// <--- Provider
  8. create: (context) => MyModel(),
  9. child: Consumer<MyModel>( // <--- MyModel Consumer
  10. builder: (context, myModel, child) {
  11. return ValueListenableProvider<String>.value( // <--- ValueListenableProvider
  12. value: myModel.someValue,
  13. child: MaterialApp(
  14. home: Scaffold(
  15. appBar: AppBar(title: Text('My App')),
  16. body: Row(
  17. mainAxisAlignment: MainAxisAlignment.center,
  18. children: <Widget>[
  19. Container(
  20. padding: const EdgeInsets.all(20),
  21. color: Colors.green[200],
  22. child: Consumer<MyModel>( // <--- Consumer
  23. builder: (context, myModel, child) {
  24. return RaisedButton(
  25. child: Text('Do something'),
  26. onPressed: (){
  27. myModel.doSomething();
  28. },
  29. );
  30. },
  31. )
  32. ),
  33. Container(
  34. padding: const EdgeInsets.all(35),
  35. color: Colors.blue[200],
  36. child: Consumer<String>(// <--- String Consumer
  37. builder: (context, myValue, child) {
  38. return Text(myValue);
  39. },
  40. ),
  41. ),
  42. ],
  43. ),
  44. ),
  45. ),
  46. );
  47. }),
  48. );
  49. }
  50. }
  51. class MyModel { // <--- MyModel
  52. ValueNotifier<String> someValue = ValueNotifier('Hello'); // <--- ValueNotifier
  53. void doSomething() {
  54. someValue.value = 'Goodbye';
  55. print(someValue.value);
  56. }
  57. }

ValueListenableProvider.gif

:::info

  • 点击’Do something’按钮因为ValueListenableProvider的作用会将”Hello”修改为”Goodbye”。
  • 使用Provider.of<MyModel>(context, listen: false)比在widget树上层使用Consumer更好,不然每次都要重建整个树。
  • 这个例子中,在widget树顶层使用Consumer并不是一个很好的做法,只是方便演示。 :::

    8. 使用ListenableProvider

    没有太好的使用场景。

    9. 使用MultiProvider

  1. import 'package:flutter/material.dart';
  2. import 'package:provider/provider.dart';
  3. void main() => runApp(MyApp());
  4. class MyApp extends StatelessWidget {
  5. @override
  6. Widget build(BuildContext context) {
  7. return MultiProvider( // <--- MultiProvider
  8. providers: [
  9. ChangeNotifierProvider<MyModel>(create: (context) => MyModel()),
  10. ChangeNotifierProvider<AnotherModel>(create: (context) => AnotherModel()),
  11. ],
  12. child: MaterialApp(
  13. home: Scaffold(
  14. appBar: AppBar(title: Text('My App')),
  15. body: Column(
  16. children: <Widget>[
  17. Row(
  18. mainAxisAlignment: MainAxisAlignment.center,
  19. children: <Widget>[
  20. Container(
  21. padding: const EdgeInsets.all(20),
  22. color: Colors.green[200],
  23. child: Consumer<MyModel>( // <--- MyModel Consumer
  24. builder: (context, myModel, child) {
  25. return RaisedButton(
  26. child: Text('Do something'),
  27. onPressed: (){
  28. // We have access to the model.
  29. myModel.doSomething();
  30. },
  31. );
  32. },
  33. )
  34. ),
  35. Container(
  36. padding: const EdgeInsets.all(35),
  37. color: Colors.blue[200],
  38. child: Consumer<MyModel>( // <--- MyModel Consumer
  39. builder: (context, myModel, child) {
  40. return Text(myModel.someValue);
  41. },
  42. ),
  43. ),
  44. ],
  45. ),
  46. // SizedBox(height: 5),
  47. Row(
  48. mainAxisAlignment: MainAxisAlignment.center,
  49. children: <Widget>[
  50. Container(
  51. padding: const EdgeInsets.all(20),
  52. color: Colors.red[200],
  53. child: Consumer<AnotherModel>( // <--- AnotherModel Consumer
  54. builder: (context, myModel, child) {
  55. return RaisedButton(
  56. child: Text('Do something'),
  57. onPressed: (){
  58. myModel.doSomething();
  59. },
  60. );
  61. },
  62. )
  63. ),
  64. Container(
  65. padding: const EdgeInsets.all(35),
  66. color: Colors.yellow[200],
  67. child: Consumer<AnotherModel>( // <--- AnotherModel Consumer
  68. builder: (context, anotherModel, child) {
  69. return Text('${anotherModel.someValue}');
  70. },
  71. ),
  72. ),
  73. ],
  74. ),
  75. ],
  76. ),
  77. ),
  78. ),
  79. );
  80. }
  81. }
  82. class MyModel with ChangeNotifier { // <--- MyModel
  83. String someValue = 'Hello';
  84. void doSomething() {
  85. someValue = 'Goodbye';
  86. print(someValue);
  87. notifyListeners();
  88. }
  89. }
  90. class AnotherModel with ChangeNotifier { // <--- AnotherModel
  91. int someValue = 0;
  92. void doSomething() {
  93. someValue = 5;
  94. print(someValue);
  95. notifyListeners();
  96. }
  97. }

MultiProvider.gif

10. 使用ProxyProvider

  1. MultiProvider(
  2. providers: [
  3. ChangeNotifierProvider<MyModel>(
  4. create: (context) => MyModel(),
  5. ),
  6. ProxyProvider<MyModel, AnotherModel>(
  7. update: (context, myModel, anotherModel) => AnotherModel(myModel),
  8. ),
  9. ],
  1. import 'package:flutter/material.dart';
  2. import 'package:provider/provider.dart';
  3. void main() => runApp(MyApp());
  4. class MyApp extends StatelessWidget {
  5. @override
  6. Widget build(BuildContext context) {
  7. return MultiProvider( // <--- MultiProvider
  8. providers: [
  9. ChangeNotifierProvider<MyModel>( // <--- ChangeNotifierProvider
  10. create: (context) => MyModel(),
  11. ),
  12. ProxyProvider<MyModel, AnotherModel>( // <--- ProxyProvider
  13. update: (context, myModel, anotherModel) => AnotherModel(myModel),
  14. ),
  15. ],
  16. child: MaterialApp(
  17. home: Scaffold(
  18. appBar: AppBar(title: Text('My App')),
  19. body: Column(
  20. children: <Widget>[
  21. Row(
  22. mainAxisAlignment: MainAxisAlignment.center,
  23. children: <Widget>[
  24. Container(
  25. padding: const EdgeInsets.all(20),
  26. color: Colors.green[200],
  27. child: Consumer<MyModel>( // <--- MyModel Consumer
  28. builder: (context, myModel, child) {
  29. return RaisedButton(
  30. child: Text('Do something'),
  31. onPressed: (){
  32. myModel.doSomething('Goodbye');
  33. },
  34. );
  35. },
  36. )
  37. ),
  38. Container(
  39. padding: const EdgeInsets.all(35),
  40. color: Colors.blue[200],
  41. child: Consumer<MyModel>( // <--- MyModel Consumer
  42. builder: (context, myModel, child) {
  43. return Text(myModel.someValue);
  44. },
  45. ),
  46. ),
  47. ],
  48. ),
  49. Container(
  50. padding: const EdgeInsets.all(20),
  51. color: Colors.red[200],
  52. child: Consumer<AnotherModel>( // <--- AnotherModel Consumer
  53. builder: (context, anotherModel, child) {
  54. return RaisedButton(
  55. child: Text('Do something else'),
  56. onPressed: (){
  57. anotherModel.doSomethingElse();
  58. },
  59. );
  60. },
  61. )
  62. ),
  63. ],
  64. ),
  65. ),
  66. ),
  67. );
  68. }
  69. }
  70. class MyModel with ChangeNotifier { // <--- MyModel
  71. String someValue = 'Hello';
  72. void doSomething(String value) {
  73. someValue = value;
  74. print(someValue);
  75. notifyListeners();
  76. }
  77. }
  78. class AnotherModel { // <--- AnotherModel
  79. MyModel _myModel;
  80. AnotherModel(this._myModel);
  81. void doSomethingElse() {
  82. _myModel.doSomething('See you later');
  83. print('doing something else');
  84. }
  85. }

ProxyProvider.gif

11. Provider builder函数和值构造函数

在前面的例子中, Provider 的model使用的是 create 创建函数来创建返回的

  1. Provider<MyModel>(
  2. create: (context) => MyModel(),
  3. child: ...
  4. )

如果我们用已存在的对象值呢?

使用value来引用:

  1. final myModel = MyModel();
  2. ...
  3. Provider<MyModel>.value(
  4. value: myModel,
  5. child: ...
  6. )

参考学习资料