The original text is here .

Although the official Flutter site ( State Management Starter app ) says that Provider is "very easy to understand", I (the author) don't think so. I think it's because there are a bit more types of Providers:

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

I just want the easiest way to manage the state of my app. Why are there so many options? Which one should I use? Where to start?

The purpose of this article is to help you understand how the main provider types are used. I'll give a simple example for each Provide type. To help you understand, you can then decide for yourself which one to use to manage the state of your app.

Prepare

My example would use a layout like this
image.png

That is:

  • That "Do something" button represents the event that changes the state of the app.
  • The "Show something" text widget represents the UI that displays the app state
  • The green boxes on the left and the blue boxes on the right represent different parts of the widget tree. UI used to highlight an event and updates related to the state changed by the event.

Here is the code:

 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'),
            ),

          ],
        ),
      ),
    );
  }
}

These examples require you to install the Provider package

 dependencies:
  provider: ^4.0.1

And it has been imported where it is needed:

 import 'package:provider/provider.dart';

Provider

As you can imagine, Provider is the most basic provider type. You can use it to provide a value (usually a data model object) anywhere in the widget tree. However, it doesn't update the widget tree for you when the value changes.

Assuming your app state is in a model:

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

You can wrap the top-level widget with a Provider to provide data to the widget tree. Use the Consume widget to get a reference to this data.

You can find Provider and two Consume widgets in the west code:

 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);
  }
}

Run the code and you will see this result:

image.png

NOTES:

  • The Hello of the mode object is displayed on the UI.
  • Clicking the "Do something" button will trigger an event that causes the model data to change. However, even if the model's data changes, the UI does not change. Because Provider will not monitor the data changes of the model object.

ChangeNotifierProvider

Unlike the basic Provider widget, ChangeNotifierProvider will listen for changes to the model object. When the data changes, it will redraw the child widget of Consumer .

In the above code, replace Provider with ChangeNotifierProvider . The Model class also needs to use the ChangeNotifier mixin (or extend it). This way you can use the notifyListeners() method. When you call this method, ChangeNotifierProvider will be notified, Consumer widget will redraw its child components.

Here is the complete code:

 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();
  }
}

Now when you click the "Do something" button, the text will change from "Hello" to "Goodbye".
image.png

NOTE:

  • In most apps, the model classes are in their own files. You need to import flutter/foundation.dart in these files to use ChangeNotifier . I don't like this very much, because it means that your business logic contains a framework dependency, and this framework is a "detail" that has nothing to do with logic. Let's do that for now.
  • When notifyListeners() method, Consumer widget will redraw all widgets of its subtree. The button doesn't have to be updated, so instead of Consumer you can use Provider.of and set listener to false. In this way, if the model changes, the button will not be updated. Here is the button widget after modification.
 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();
      },
    );
  }
}

The code below is still the same, using the Consumer component.

FutureProvider

FutureProvider is basically a wrapper widget for FutureBuilder . You can set some initial data displayed by the UI, and then you can give a Future value. FutureProvider will notify the Consumer to redraw its widget subtree when the Future completes.

In the code below I provide an empty model to the UI. I also added a method that returns a new model object after 3 seconds. This is FutureProvider is waiting for.

Like the base provider, FutureProvider does not listen for any changes to the model itself. This is shown in the code below.

 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);
  }
}

image.png

NOTE:

  • FutureProvider will notify Consumer Futuer<MyModel> when it is finished.
  • Click Hot restart to reset the app
  • Note that clicking the "Do something" button will not update the UI, even after the Future has completed. If you want to make the UI redrawable, use ChangeNotifierProvider .
  • FutureProvider is generally used when processing network or text reading data. However, FutureBuilder can also be used for the same purpose. In my (author's) half-baked opinion, FutureProvider is no more useful than FutureBuilder . If I need a provider, I basically use ChangeNotifierProvider , if I don't need a provider I probably use FutureProvider .

StreamProvider

StreamProvider is a wrapper for StreamBuilder . You provide a stream, and Consumer will update its widget subtree when the stream receives an event. This is very similar to the above FutureProvider .

You can treat the value sent by the stream as immutable. That is to say, StreamProvider will not monitor model changes. Only listen for new events in the stream.

code show as below:

 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 Notify after new event Consumer redraw
  • reset the app with hot restart
  • Clicking "Do something" doesn't redraw the UI. If you want the UI to be updatable, then use ChangeNotifierProvider . In fact, you can add a stream to the model and call notifyListeners() . This is completely unnecessary StreamProvider .
  • You can use StreamProvider to implement BLoC mode

ValueListenableProvider

You can skip this section, it's basically the same as ChangeNotifierProvider just a little more complicated and with no added benefit. . .

If you have one like this ValueNotifier :

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

Then you can use ValueListenableProvider to listen for changes in this value. However, if you want to call the model's method in the UI, then you also need to provide the model object in the provider. Therefore, you will see in the code below a Provider which provides a MyModel object to Consumer , and this provider also gives ValueListenableProvider It needs the ValueNotifier property someValue contained in MyModel 5321a93d3e24a50805ce95f1d00bc9a8---. That is, if you only need to monitor the attribute value of a model object, you only need ValueListenableProvider , but if you want to call the method of this model in the UI, you need to write an additional Provider .

The complete code is as follows:

 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);
  }
}

image.png

However, the author's own views on the above have changed later, and he recommends everyone to read this article .

ListenableProvider

If you want to customize a provider, then you need to use this provider type. Still, the documentation reminds you that you might just need one ChangeNotifierProvider . Therefore, this content is temporarily ignored. I (the author) will update this section later.

MultiProvider

So far, our examples have only used a model object. If you need another model object, you need to nest the provider (as in the explanation ValueListenableProvider ). However, nesting only creates confusion. A better way is to use MultiProvider .

In the following example ChangeNotifierProvider will be used to provide two model objects.

Below is the full code. a bit long. Just pay attention to MultiProvider , Consumer and two model classes.

 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();
  }
}

image.png

NOTES:

  • Click the first "Do something" button and the text will change from Hello to Goodbye . Click the second "Do something" button and the text will change from 0 to 5 .
  • This is not very different from a single ChangeNotifierProvider . Consumer Get the corresponding model object through different type parameters.

ProxyProvider

If you have two model objects to provide, and there are dependencies between the two model objects. In this case you can use ProxyProvider . One ProxyProvider accepts an object from one provider and injects the object into another provider.

At first you will be a little confused about the usage of ProxyProvider , this example can help you to understand.

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

The basic ProxyProvider has two type parameters. The second depends on the first. In the ProxyProvider udpate method of ---e0a728371e9212fb7f5d9efc6c2122c3---, the third parameter has anotherModel which stores the last value, but we don't use it here. We just pass myModel as a parameter to the constructor of AnotherModel .

Full code:

 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');
  }
}

image.png

NOTES:

  • The start text Hello .
  • When you click the "Do something" button, the MyModel object's text will become Goodbye . MyModel notifies ChangeNotifierProvider and the UI also shows the new text.
  • When you click the "Do something else" button, AnotherModel receive MyModel ( ProxyProvider inject), then modify the text to "See you later". The UI is updated because the listener is notified when MyModel changes. If AnotherModel changes its value, the UI will not update. Because ProxyProvider does not listen for changes. At this time, ChangeNotifierProxyProvider can be used.
  • ProxyProvider still confuses me a bit. ChangeNotifierProxyProvider itself has more and more instructions, so I'm not going to introduce more.
  • I (the author) FilledStack uses GetIt better than ProxyProvider for dependency injection.

Provider builder and value constructor

One last thing to explain.

Most providers have two types of constructors. A basic receive a create method to create your model object. We basically do this in the above example:

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

You can see that the MyModel object is created in the create method.

If your object has already been created, you just want to provide a reference to it. Then you can use the named constructor value :

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

Here MyModel the object is built ahead of time, and only a reference is provided. You can do this if you have already created the object in the initState method.

Summarize

Basically you can ignore most classes in the provider package. Just remember how to use ChangeNotifierProvider and Consumer . If you don't want to update the UI then use Provider . For scenarios using Future and Stream, you can put them in your Model class and notify ChangeNotifierProvider . FutureProvider and StreamProvdier are not required. Most of the time MultiProvider is not needed. If there is dependency injection, you can also use the GetIt package to achieve it, you don't need to use ProxyProvider . This article has a more in-depth introduction.

Next up: Riverpod

You may have heard of Riverpod, a state management library built on top of providers. The author is also the same author. If you are still confused about the use of providers, then riverpod will only confuse you even more. However, once you get familiar with this library, it makes everything easy. I (the author) suggest you give it a try. I wrote a Riverpod tutorial that I hope will help you.


小红星闪啊闪
914 声望1.9k 粉丝

时不我待