Flutter is witnessing a meteoric rise. With the introduction of Flutter 2.0 along with full web support, it feels like magic that you can generate a web-app and a mobile app from a single code base. Just. Amazing.
But with the rise of Flutter and it’s ecosystem, comes the troubles of doing one thing in a myriad of ways. One such trouble is the number of packages available to manage state in your Flutter application.
With more demanding users, even the most simple use-case are becoming complex to implement in a mobile app. Need to update the number of items in the cart globally after adding a product in it? You need a state management solution. Need to maintain the login state of the user throughout the app? You need a state management solution.
The funny part is, previously we tend to ignore global variables, but now, we are embracing them in the form of global states. How times have changed. It’s not too long that we’ll start using “goto” statements in the code as well. (Just kidding. No one should ever use “goto”.)
The Current State of State Management Techniques
State management is an important part of a mobile app architecture. Choosing the right technique helps in building an app which is extensible and testable. If you’re not using a state management solution in your app, then you should look into it more seriously.
In the context of Flutter, there’s a whole ecosystem of state management packages. The developer community is a bit divided at the moment and has not yet decided the best technique because there’s no silver bullet.
A good developer is one who can choose her tools based on the problem at hand. As the saying goes, if you’ve only ever learned to use a hammer, every problem will look like a nail. Hence, one should always understand the pros and cons of multiple tools before making any decision.
Here’s a brief introduction of most used state management packages and techniques-
- BLoC– It stands for Business Logic Components. According to BLoC, everything in the app must be represented as a stream of events. From button clicks to changes in text fields. Events from one widget enters the stream and other widgets respond. The BLoC sits in between and controls the flow. For large applications, BLoC can be used to create proper separation of concerns. BLoC, however, can be also be daunting to look at. One has to write a lot of boilerplate code to implement it correctly. Read more about the BLoC package here.
- Provider– When used along with ChangeNotifier, Provider is a very simple yet scalable technique to implement MVVM architecture in a Flutter app. Provider is essentially a dependency injection package which supports notifying child widgets using the ChangeNotifier. We’ll learn more about it later in the article. It’s also one of Google’s recommended techniques. It uses InheritedWidgets under the hood.
- setState– This comes in-built with Flutter’s Stateful Widget. It’s something that beginners and small apps should use in order to keep things simple and understandable. Unless your Flutter app has more than a few screens (more than 4) and needs to manage a global state, you should use setState(). Read more about it here.
There are more packages like GetIt, GetX etc, you can read more about them here.
The advantages of using Provider
There are many advantages of the Provider package specially when combined with ChangeNotifiers-
- Its very easy to understand and reason about. The documentation is clear and concise and provides guidelines to best practices as well.
- You can use both reactive and non-reactive ChangeNotifiers in a Widget which means that you can depend on other ChangeNotifiers without being notified about changes. This provides many performance benefits.
- You can use Provider as a simple dependency injection container as well. The only thing is, you need to have access to the BuildContext attached the widget tree.
The MVVM Architecture
The MVVM architecture is rather simple to understand and is highly applicable to reactive frameworks like Flutter.
As you can see above, the ViewModel is responsible for holding the business logic and updating the view as the data changes. In a simple app, the model can be used to fetch data from data sources like SQLite Database or a REST API. In larger apps, however, we should use a service layer which will communicate with the data providers, populate the models and then return them to ViewModels. In this way, the ViewModel uses services and updates the views.
The question now is- how can a ViewModel update a View? The answer is simple- ChangeNotifier.
ChangeNotifier is a class which notifies the listeners which are attached to it when notifyListeners() is called. When coupled with Provider, it can injected into any view and hence making the view reactive to changes.
Here are the basic things to keep in mind when working with the MVVM architecture in Flutter using Provider and ChangeNotifier-
- Every View should have it’s own ViewModel.
- Use service classes to get data from external or internal data sources.
- The View can not directly use any service class. It’ll always use it’s own ViewModel to communicate with services, even if the communication is non-reactive in nature.
- Inject the ViewModels (which extends ChangeNotifier) as up as possible in the Widget tree. More specifically, inject them in the root widget tree. This will help in maintaining global state throughout the application easily.
- Do not overuse reactive data because ChangeNotifier takes O(N) time to propagate changes where N is the number of listeners attached to it.
- Do not call notifyListeners() unnecessarily. It causes whole Widget tree to rebuild every time its called (unless listen is false in Provider.of)
This concludes the Part 1 of this series about Flutter and Provider. In the next part, we’ll look at the boilerplate code that we use in building Flutter apps.