Flutter has an ever-increasing ecosystem of state management solutions. The flutter documentation itself lists out more than 10 options! It is a daunting task to figure out which solution to choose.
Before we get into any more details let's first get a very basic concept out of the way. Flutter is a declarative UI framework. That means we write how the UI should look given a particular state.
UI = *f(*state*)*
Keeping this in mind helps in writing better flutter code that plays well with the framework.
Now let's take a look at two of the most widely adopted state management patterns.
Note: This article assumes familiarity with flutter and the dart programming language.
Google introduced the BLoC pattern at the 2018 Google I/O developer conference. This pattern was originally used by the Google Ads team for their flutter app. It was created to handle complex state changes, which was difficult to do with the solutions present at that time.
BLoC is a direct implementation of the UI = f(state) concept. It combines this concept with a fundamental part of the dart language - Streams.
A stream is a sequence of asynchronous events. We can consider UI interactions within an app as a stream of events as well.
The BLoC receives these events, performs some processing on the events, and outputs a stream of states.
flutter_bloc is one such state management package that makes it easy to implement the BLoC pattern. Since everything is built on top of dart streams, it has great support for testing.
The flutter bloc package also has some companion packages that help in implementing some complex use cases such as restoring state and adding undo-redo functionality.
One thing to note is that BLoC is just a component that consumes events and emits states. We still need to use something to provide this BLoC in the widget tree. Usually, provider is used for this purpose.
Let us create a BLoC for searching cities and displaying the list of search results.
1. First, we create events that the BLoC will consume.
2. Create the states that the UI will consume
3. Create the BLoC. Here we process the incoming events and return states.
4. To access the bloc in the widget tree, we provide it with BlocProvider
5. We retrieve the bloc provided above. Add new states to the BLoC from the UI. The BLoC will process these events and emit states in return.
6. The UI will consume these states and update the view
First we define the events that can occur within the app. We also defines the states the app is supposed to display.Then as the user interacts with the app, events get added to the BLoC which performs the necessary actions. The BLoC then emits states which are received by the UI.
In short, Events go into the BLoC and States come out of the BLoC.
BLoC was difficult to understand for many. Always using streams of data did not seem like an elegant solution in most use-cases. A simpler solution was needed to handle state in flutter.
The second problem was that providing some data down the widget tree was difficult to do using the InheritedWidget.
Although, we call this approach the provider pattern, the provider package in itself is just a wrapper around InheritedWidget with some added functionality. It can provide any type of object down the widget tree. The provider itself does not hold or mutate state. For that, we can use a ChangeNotifier. A ChangeNotifier is a class that provides listening capabilities to any class that extends it or uses it as a mixin.
Adding listeners to a change notifier is an O(1) operation. While removing listeners and dispatching updates to its listeners is an O(n) operation.
Instead of a change notifier, we can also use a State Notifier. state_notifier is a package created by the same author who created the provider package, so it integrates very well with it. It is more efficient than a change notifier and makes testing easier.
Let's implement the same cities search use-case
1. First we create a class that extends ChangeNotifier.
2. Similar to BLoC we first need to provide this class in the widget tree. A special type of provider call the ChangeNotifierProvider is used for this.
3. We then retrieve the the model class where we need and call the searchCities function. The model will do the necessary fetching of data and then call notifyListeners().
4. The UI will listen to the model and update the views. The consumer widget adds a listener to the model. Whenever notifyListeners() is called, the builder will be called again and the view will update.
Any good state management solution should satisfy these basic requirements
Both of these approaches satisfy all three requirements. But each has its own set of drawbacks.
The BLoC pattern has a steep learning curve. It adds a lot of boilerplate to the codebase.
The provider pattern gets slower as we add more listeners. The ideal solution would be a combination of both. We need to pick the right tool for the job at hand.
BLoC is very powerful with streams as its backbone. But you won't call a fire truck to put out a campfire.
Use BLoC in situations where you need the capabilities it provides. Such as having more than 2-3 listeners active at a time or maintaining and navigating through the history of state changes or debouncing and transforming the incoming events.
Use the provider pattern for most if not all of your state management needs. It's a more than capable solution and you may never need to use BLoC.
With both approaches becoming more mature over the past few years, there have been some updates to these patterns that make it easier to work with them.
BLoC is now a subclass of the Cubit class. Cubit works the same as BLoC but it attempts to reduce some of the boilerplate that we need to write with BLoC by replacing event classes with simple functions.
The creator of the provider package has created a new package called riverpod. The tag line on its website reads "Provider, but different". It works on the same concepts as the provider but tries to fix some pitfalls of the original package. But the original provider package is still the most popular and the recommended solution in official flutter documentation.
Head over to the Flutter Template on Wednesday's GitHub to view the complete implementation of the examples we saw in this article.