background

Long ago, a friend in our QQ group has been wanting to make me out of a Provider (https://github.com/rrousselGit/provider) tutorial, but I haven’t had the promises. Because I think if I write an introductory tutorial, it’s already documented, it’s already written, and if I want to go deeper, I don’t know. But not so much these days, because of the hydrology.

State management

When it comes to Flutter, it’s hard to avoid state management. React developers are familiar with state management; But for us pure native developers, it’s still a little strange. Flutter is declarative, which means that Flutter updates the UI to reflect the current state of the app:

Flutter
setState()
setState()

One situation is when we are in a page:

Widget
{{{{}}}}
Widget
setState()
setState()
Widget
Widget
setState
Widget
Widget

In general, in real development, it is possible to share data across pages:

The image above shows a shopping cart feature. When the user clicks Add, the item is added to the shopping cart. When the user clicks the shopping cart, we can see the item. How would we do that without state management?

All this is to say that using state management is more important. In a nutshell, this is how to quickly and easily share data between widgets and display it on a page.

The state management methods of Flutter include but are not limited to Provider, Bloc, Redux and fish-redux.

  • BlocIt’s actually an idea, and it was the first state management I used, and now there’s an implementation library, and it’s generally based on responsive programming.
  • ReduxforReactIt’s no stranger to developers, after allFlutterThis is also borrowedReact.
  • Fish-ReduxBased on aReduxAli produced, generally more complex, suitable for medium and large projects. Now society is also generatingFish-ReduxTools for template code
  • ProviderisGoogleRecommended state management, the second state management I use, is relatively simple and easy to worry about.

Next, I’ll briefly introduce the use of providers.

I met the Provider

The Provider is essentially an encapsulation of the InheritedWidget. Using providers has many advantages over using inheritedWidgets directly, such as simplifying allocation and disposal of resources, enabling lazy loading, and so on.

Provider provides us with a number of different types of providers. To see all types of providers, click here

name description
Provider The most basic provider. It takes a value and exposes that value, whatever that value is.
ListenableProvider forListenableObjectprovider.ListenableProviderWill listen for changes in the object, as long asListenableProviderListner is called,ListenableProviderThe controls that depend on the provider are rebuilt.
ChangeNotifierProvider ChangeNotifierProviderIt’s a special kind ofListenableProvider, which is based onChangeNotifierAnd it will be called automatically when neededChangeNotifier.dispose.
ValueListenableProvider Listening to theValueListenableAnd only exposedValueListenable.value.
StreamProvider Listen to aStreamAnd expose the most recently committed value.
FutureProvider Carry aFuturewhenFutureWhen it’s done, it updates the controls that depend on it.

In view of my lack of knowledge, this article will not explain how to use various providers one by one, so this article chooses the ChangeNotifierProvider I use most to explain, hoping to introduce a brick.

Create a Proivder

Generally, there are two ways to create a Provider:

  • Default constructor
  • .valueA constructor

When we create a new object, we use the default constructor instead of using the.value constructor, because if we create an object through the.value constructor it could cause a memory leak or some unexpected problems. Here’s a quick explanation of why you can’t create an object with value. Because the build method in Flutter should be clean and free of side effects, many external factors trigger rebuild, such as:

  • The routing of pop/push
  • Screen resizing is usually due to keyboard changes or screen orientation changes
  • Parent control redraws child control
  • Depends on theInheritedWidgetControl (class.of (context) section) has changed

So, the problem with using.value to create objects is that it makes build impure or has the side effect of making build calls from outside the building cumbersome. This problem ends here, the friend that likes research can explore by oneself.

  • touseProviderthecreateCreate an object in.
Provider(
  create: (_) => MyModel(),
  child: ...
)
Copy the code
  • Don’tuseProvider.valueCreate an object.
ChangeNotifierProvider.value(
 value: MyModel(),
 child: ...
)
Copy the code
  • Do not create objects from variables that can change over time. Because in this case, even if the referenced variable changes, the object we create will not be updated.
int count;

Provider(
  create: (_) => MyModel(count),
  child: ...
)
Copy the code

If you want to pass variables that change over time into our objects, consider using ProxyProvider:

int count;

ProxyProvider0(
  update: (_, __) => MyModel(count),
  child: ...
)
Copy the code

Note: When using the Provider’s create/update callback, it is important to note that by default the create/update call is lazy. This means that create/ Update will only be called if the data in our Provider is requested at least once. If we want to do some preprocessing, we can disable this feature with the lazy parameter:

MyProvider(
  create: (_) => Something(),
  lazy: false.)Copy the code

Read the data in the Provider

The easiest way to read data is to use the BuildContext extension method:

  • Context.watch (), which uses the corresponding control to listen for changes in T.
  • Context.read (), which returns T directly and does not listen for changes.
  • Context. Select

    (R cb(T value)), this method causes the corresponding control to listen for only a small part of T changes.
    ,>

Of

(context), which behaves much like watch/read, is how we used to get data before the extension methods above.

These methods look up from the control tree, starting with the control associated with BuildContext that is passed in, and eventually finding and returning the nearest variable to type T (or throwing it if not found).

Note that the complexity of this operation is O(1). In fact, this does not move through the control tree.

Speaking of now, nothing more than the translation of the document, now let’s go code office ~~~

Show me the code

The story starts with the counter of the Flutter project, because this is the counter of the newly created Flutter project template. Now we will use the ChangeNotifierProvider to modify this project simply.

  • willMyHomePagebyStatefulWidgettoStatelessWidget.
  • useChangeNotifierProviderTo update the page

First version

First, we will create a ChangeNotifier:

class MyChangeNotifier extends ChangeNotifier {
  int _counter = 0;

  int get counter => _counter;

  incrementCounter() {
    _counter++;
    notifyListeners();// Call this method to update the UI}}Copy the code

Currently we call MyChangeNotifier’s incrementCounter method when we click on FloatingActionButton. Note that when we are done with the business, NotifyListeners are called to notify the Provider of UI updates if needed.

Next we implement our UI.

First, we will create MyHomePage. The UI layout will use the layout in Example instead of the StatelessWidget. We then fetch MyChangeNotifier instance with BuildContext. Note that when we click on FloatingActionButton, we don’t call setState (crap, StatelessWidget can’t setState either), but our UI will still be updated. The code is as follows:

class MyHomePage extends StatelessWidget {
  final String title;

  MyHomePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    MyChangeNotifier notifier =
        Provider.of(context); // Obtain MyChangeNotifier through provider.of (context)
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${context.watch<MyChangeNotifier>().counter}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: notifier.incrementCounter,// We expect to output the number of clicks when clicked
        tooltip: 'Increment', child: Icon(Icons.add), ), ); }}Copy the code

Now we will wrap MyHomePage with the ChangeNotifierProvider. This will ensure that MyHomePage will get its MyChangeNotifier instance with BuildContext.

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     title: 'Flutter Demo',
     theme: ThemeData(
       primarySwatch: Colors.blue,
       visualDensity: VisualDensity.adaptivePlatformDensity,
     ),
     home: ChangeNotifierProvider(
         create: (_) => MyChangeNotifier(),
         child: MyHomePage(title: 'Flutter Demo Home Page'))); }}Copy the code

Now that the code is finished, run this command to see if it looks exactly the same as example.

Of course, we can define a field called outputMessage in MyChangeNotifier and assign a value to the Text in MyHomePage.

Text(
    context.watch<MyChangeNotifier>().outputMessage,
    style: Theme.of(context).textTheme.headline4,
    ),
Copy the code

Version 2

Now that we’ve learned the basics of how to use the ChangeNotifierProvider, let’s make a simple change to the code above.

  • willChangeNotifierProviderMove to theMyHomePage.

Very simple, the code is as follows:


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page')); }}class MyHomePage extends StatelessWidget {
  final String title;
  final MyChangeNotifier notifier = MyChangeNotifier();

  MyHomePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => notifier,
      child: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'You have pushed the button this many times:',
              ),
              Text(
                '${context.watch<MyChangeNotifier>().counter}',
                style: Theme.of(context).textTheme.headline4,
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: notifier.incrementCounter,// We expect to output the number of clicks when clicked
          tooltip: 'Increment', child: Icon(Icons.add), ), ), ); }}Copy the code

While we were happily running the above code, we ran into some problems:

Consumer

ConsumerThe use of

There is no magic in Consumer itself, no fancy implementation. Simply use provider.of in a new control and delegate the build method of that control to the Builder. This Builder will be called multiple times. It’s that simple.

The Consumer was designed for two reasons

  • When ourBuildContextDoes not exist inProviderWhen,ConsumerAllows us to takeProviderGet data from.
  • Performance is optimized by providing more subtle redraws.

The first is what we have here, and the second is for readers to explore. So, we can add a Consumer to solve the above ProviderNotFoundException problem:


class MyHomePage extends StatelessWidget {
  final String title;
  final MyChangeNotifier notifier = MyChangeNotifier();

  MyHomePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => notifier,
      child: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: Consumer<MyChangeNotifier>(
          builder: (_, localNotifier, __) => Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  '${localNotifier.counter}',
                  style: Theme.of(context).textTheme.headline4,
                ),
              ],
            ),
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: notifier.incrementCounter,
          tooltip: 'Increment', child: Icon(Icons.add), ), ), ); }}Copy the code

Running again, isn’t it perfect?

Provisional summary

Time is limited, originally wanted to write in one breath, but the Internet era does not play hungry marketing how ashamed to say that they have mixed the Internet…

This article is quite simple as the first one to introduce a Provider. After all, it just changes the Flutter example. In the following articles, I’ll cover more Provider usage and issues, as well as more complex demos.

To be continued… I don’t expect you to decide.