DEV Community

Crizant Lai
Crizant Lai

Posted on • Originally published at Medium

Flutter: State management with ChangeNotifier

Tl;dr: Go visit this package and see the example section.

What is State Management?

For those of you who are new to flutter: There should be no app which only display a single static screen, right? An app like this is no difference than an image. Every app should have certain parts of UIs which depends on some data, that data is called state. The process of structuring the state, and making the UI refreshes upon the change of the state is hence named state management.

State Management with ChangeNotifier

In Flutter there are many ways to do state management. One of the ways is by using ChangeNotifier. Comparing to the setState method, using ChangeNotifier lets you share the state among different widgets of your app, without having to worry about passing around the properties or “on change callbacks” by widget parameters.

Let’s start by creating an instance of the model:

class CounterModel extends ChangeNotifier {
  int _count = 0;
  void increment() {
    _count++;
    notifyListeners();
  }
  int get count => _count;
}
Enter fullscreen mode Exit fullscreen mode

Simple enough. It has an integer count value, and a method to increase the value by 1.

Then we create an instance of the model somewhere:

final CounterModel counterModel = CounterModel();
Enter fullscreen mode Exit fullscreen mode

Then the UI. Here comes the real trick:

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('My Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            ChangeNotifierBuilder(
              // supply the instance of `ChangeNotifier` model,
              // whether you get it from the build context or anywhere
              notifier: counterModel,
              // this builder function will be executed,
              // once the `ChangeNotifier` model is updated
              builder: (BuildContext context, CounterModel? counter, _) {
                return Text(
                  '${counter?.count ?? ''}',
                  style: Theme.of(context).textTheme.headline4,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _counterModel.increment,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Wrap the portion of your widget, which depends on the value of the model, in a ChangeNotifierBuilder. The UI will be updated once the notifyListeners method inside the model is invoked. The smaller the portion is, the faster the rebuild.


ChangeNotifierBuilder

Wait. You said there is no such thing as ChangeNotifierBuilder?

You’re right! This is a package created by me. Or you can use AnimatedBuilder instead, they are pretty much the same thing.

But I created the package for 3 reasons:

  1. For the purpose of state management, ChangeNotifierBuilder is a more reasonable and readable name than AnimatedBuilder, for a builder widget.

  2. Sometimes our model is not yet ready (i.e. equals null). In this case if you use AnimatedBuilder, it throws "animation cannot be null" exception. However, if you use ChangeNotifierBuilder, the runtime value of notifier can be null.

  3. The builder method provides you the T notifier object as a parameter, which is a bit more user-friendly.

  4. You can’t listen to multiple ChangeNotifiers with AnimatedBuilder, so I created MultiChangeNotifierBuilder for this purpose.


Optimizing Performance

There are 3 techniques to boost performance when using ChangeNotifierBuilder and MultiChangeNotifierBuilder:

  1. Never wrap the builder around the root of your widget tree. Since we want to minimize the number of widgets to rebuild on every update, try wrapping the builder around the widgets which depend on the model only (the further away from the root the better, i.e. the leaves).

  2. If the builder callback’s return value contains a subtree that does not depend on the model, you may pass it as the child parameter, instead of rebuilding it every time.

  3. Don’t supply models which are not depended by the widget tree. Because unnecessary rebuilds will be triggered when those models update.


Putting it all together

The final piece of the puzzle is, how do you pass the same instance of the model around your app?

You can do it by creating a singleton model, or using the fantastic community packages like provider or get_it.

Discussion (0)