Flutter: State management with ChangeNotifier
Tags: flutter, statemanagement, dart
Published: 2021-07-25 10:23:14

Tl;dr: Go visit [this package](https://pub.dev/packages/change_notifier_builder) 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:
```dart
class CounterModel extends ChangeNotifier {
  int _count = 0;
  void increment() {
    _count++;
    notifyListeners();
  }
  int get count => _count;
}
```

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:

```dart
final CounterModel counterModel = CounterModel();
```

Then the UI. Here comes the real trick:
```dart
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),
      ),
    );
  }
}
```

Wrap the portion of your widget, which depends on the value of the model, in a `ChangeNotifierBuilder`. The UI will be updated once the `notifyListener`s 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](https://pub.dev/packages/change_notifier_builder). 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 `ChangeNotifier`s 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](https://pub.dev/packages/provider) or [get_it](https://pub.dev/packages/get_it).