Providers with ChangeNotifiers
The Chronicles of Flutter state management 7.
If you have gone through in some fashion and had some exposure to the Provider package, you would have realised how awesome it is in tackling the problem it needs to. As discussed in my previous article, Providers are built on top of the Inherited Widget class and so easily it takes on the benefits of using InheritedWidgets but yet solves most of the issues or difficulties often experienced when using the InheritedWidget class. Issues like.
- Having too much boilerplate code (which is cumbersome and difficult to maintain)
- Having too much-nested code, which makes inherited widgets difficult to factorize and can be confusing.
- Nesting hell, when we have more than a few InheritedWidgets that we want to use or wrap a single widget.
In the previous article, we saw how using only the Provider class and a simple model class we were able to make available the model class to widgets with very minimal code. But that is not the only reason we set out to use the Provider package, we want to be able to provide values that when they change our UI should reflect the changes.
How then can we be able modify values in our model, such that changes can be reflected upon in the dependent widgets?
We have ChangeNotifiers.
“A class that can be extended or mixed in that provides change notification API using VoidCallBack for notification.”
The whole concept of changeNotifiers that make this possible is that, we are able to create a model class that can be listened to, such that when there is a change, it emits some kind of a signal. The Provider also is capable of subscribing and listening to changes such that when the model class internally changes and emits a signal, it should provide the new values to all the widgets depending on it.
So in essence, to make our model class Listenable, we inherit (or use mixing) from the ChangeNotifier class. These ChangeNotifier classes internally are subclasses of the Listenable class, ironic right? To make our model class Listenable, we need to extend a Listenable.
Inheriting a Listenable such as ChangeNotifier, gives us access to the method notifyListeners (this is that VoidCallBack) which is purposeful for just notifying or emitting signals when changes have been made.
As seen, the listener is similar in function to the setState function in that it notifies the appropriate listening class or widget of some imminent changes and proper action (rebuild) can be made.
Now instead of the Provider class, we subscribe to this model using the ChangeNotifierProvider. The difference between the ChangeNotifierProvider and the Provider is that: The Provider takes a value and exposes it to all the widgets, but it does not listen to changes from that value.
So in essence is it some form of a global variable for widgets?
Yes, in essence, the Provider acts as some sort of global variable for widgets and is used as such (sometimes to just cache values and use them when necessary) the difference with the typical global variables is that, instead of giving global access to the value, it gives scoped access of that value to a particular subtree of widgets, this prevents the tight coupling that comes with global variables and also solves the problems with constructor injection. You can checkout this article if you want to learn a little more.
Back to where we were.
The difference between ChangeNotifierProvider and Provider. So yeah, Providers dont just notify for changes in a nutshell. A ChangeNotifierProvider however is a specification of a type of Provider for Listenable Objects (models), which will then listen to the model and tell the widgets that depend on it to rebuild if the model’s value changes.
ListenableProvider: A specific provider for Listenable object. ListenableProvider will listen to the object and ask widgets which depend on it to rebuild whenever the listener is called.
ChangeNotifierProvider: A specification of ListenableProvider for ChangeNotifier. It will automatically call ChangeNotifier.dispose when needed.
As we see, it is provided just like the normal provider, it is as a matter of fact also consumed the same.
As seen above we consume the values almost similarly, but there is some kind of difference in that there is the “listen: false” argument in the call to the Provider.of() in the onTap.
But why so?
This is because, by default, when calling Provider.of() without specifying, we are listening to changes, but flutter does not allow the possibility to listen for changes when we are calling Provider outside of the build method, this is because in flutter’s own words, “It is unsupported because may pointlessly rebuild the widget associated to the event handler, when the widget tree doesn’t care about the value.” which makes sense because we don’t want the UI to be rebuilt (when a value changes) because an external method that has nothing to do with the UI is using a value that is not used in building the UI.
For more clarity,
As stated, different state management solutions arise because of the need to uphold proper development practices by developers.
Looking at our model class though simple something still strikes us, shouldn’t classes be immutable?
Why then is our model class mutable, is this some kind of exception or something is wrong?
Actually, ChangeNotifiers is not without its drawbacks, and in as much as in trivial cases mutability of classes might be accepted, we as “Good Programmers haha” will like to uphold the practice of immutability of classes. But if we add final fields into our model class, then changing (updating) them as seen above will not be possible anymore and we will need to perform some kinds of gymnastics to get this done. Even when we perform gymnastics, we won’t be able to perfectly handle changes and they start to become a lot of boilerplate code.
Secondly, we have issues where if we have more than one field in our model class, an update to one of the fields will directly mean a rebuild to any widget consuming that class regardless of the specific value it is consuming. Though Providers provide a new way of being specific as to the value whose change should trigger the rebuild (in the select extension method; we will see more of that in a future note), that’s a new complexity and scenario that we need to handle every time we consume information, but surely we might not always be sure when those might be necessary or not.
We will be looking in the next article StateNotifiers with providers and how it helps to improve and bring in its own perks that make state management a little more satisfying in its own right.
For more awesome Flutter content, follow Flutter Community on Twitter: https://twitter.com/FlutterComm