This post is written by Alessio Salvadorini
State management is a very controversial topic in the Flutterverse, with many holding strong opinions on it.
In the last few weeks, my Twitter feed has been overwhelmed with threads about state management, covering the entire spectrum of opinions, from GetX/BLoC/Riverpod fanatics on one side to a few rare enlightened ones on the other side who recommend not using any state management at all.
While I personally enjoyed almost all of them, the impact on the Flutter community has been quite severe.
When I asked my readers about writing this article in a typical TDD (Twitter-Driven Development, ©® @mkobuolys) approach, about a third voted no, and half of them were so fed up with state management that they recommended that I shoot myself, metaphorically speaking.
So, keep all this in mind while I walk you through this article about state management to show you how to choose the best state management solution for Flutter at the end of 2022 (and perhaps in 2023 as well). It’s worth noting that this advice is based entirely on my own opinion, and you’re always welcome to disagree. However, we first need to understand how and why we ended up in this situation, so bear with me for a few minutes longer.
The state of Flutter state management: How did we get here?
If you haven’t done so yet, I strongly recommend you watch these two videos: the first shows Filip Hracek and Matt Sullivan on the Google I/O stage in May 2018 introducing BLoC (the pattern) to all of us; the second shows the same Filip and Matt on the very same stage but in May 2019, exactly one year later, introducing pragmatic state management.
However, in between those two videos, I think something important yet fleeting happened. A big misunderstanding was born, and it impacted our community more than I initially realized.
To understand the context at the time when the first video was released, we need to remember that the community was already asking Google, and the Flutter team in particular, for a recommended approach for state management. Also, Flutter wasn’t as relevant as it is nowadays. This meant that very few videos at Google I/O were about Flutter, so one about state management felt even more relevant — or at least, the community perceived it to be.
Shortly after the first video was released, a talented developer who needs no introduction, Felix Angelov, started to develop BLoC (the package).
It quickly became the de facto standard for state management, one of the most used packages by individual developers and big companies, and the subject of mandatory questions at job interviews. Basically, it was essential for anyone who wanted to become a Flutter developer to understand it.
This happened around the time when I was starting to look into Flutter. I quickly reached a point when the more I was coding in Android, the more I wanted to code in Flutter, and the more I was coding in Flutter, the more I wanted to code in Flutter. So, by the time I tried to wrap my head around BLoC (the pattern) and BLoC (the library), the second video was released, introducing what immediately felt like a much simpler approach to state management.
But, as the demand for a state management solution had already been met (or at least the community thought so), and everyone was already using BLoC (the package), I think the second video went almost unnoticed.
It does, however, contain this little gem starting at around 19:20, where Filip and Matt mention that even though Google also open sourced a similar package based on scope model, they would instead recommend Provider, a library created by a young and talented French developer, Remi Rousselet.
You can find a non-exhaustive list in the official Flutter docs.
Given all that, it’s quite easy to understand how, by the end of 2022, we’re still in a messy and unclear situation.
It’s frustratingly difficult for beginners and new developers approaching Flutter to make a choice unless they have lots of time to spend learning about the topic.
The Flutter team has been mainly silent about this topic, and I don’t blame them, as they can’t (and, IMHO, shouldn’t) suggest one state management technique that would be suitable for all kinds of apps, nor can they add one directly into the SDK.
The only way to resolve this situation is to end at the beginning.
Isn’t this where we came in?
Believe me or not: Not all states need a state management technique. A state can be classified into one of two distinct types: ephemeral or application state.
You can think of the ephemeral state as a state that is self-contained into a widget, which doesn’t need to be shared or accessed from anywhere else.
An example of an ephemeral state is the index of the actual page in a page viewer. For an ephemeral state, you don’t need any state management technique, and setState() and StatefulWidget work perfectly fine for handling this case.
Think of the application state as a state that is shared between the different views of your app or that needs to be restored between two separate user sessions.
In this case, you need to move the state away from your view and into a state holder class. Examples of an application state are the settings or the login.
For an application state, you do need a state management technique.
Since you are going to pick only one, you want to choose carefully. And to choose carefully, you need to be able to compare your options. And to be able to compare, we need to agree on the metrics of the comparison.
Introducing metrics: Measuring how good a state management solution is
The only way we can choose which one is the “best” state management solution is to agree on how we define “best.”
There are many different types of apps, and more will come in the future that we can’t even imagine now. Therefore, it’s practically impossible to suggest one state management technique to rule them all.
So far, I have found only two sources that have helped me compare state management techniques.
The first source is by my mate Mike Rydstrom, who created an exhaustive picture comparing the top 30 state management packages:
As you can see, this picture ranks the packages by package likes. It also includes extra information, like if a given package is null safe (NS), the percentage of test coverage (CodeCov), the percentage of API docs’ completeness (API docs), a summary (Points), the package’s popularity, GitHub stars, the ratio of likes to stars, and the ratio of open to closed issues.
That’s a lot of info to crunch for one single picture.
It’s a good start, with Provider, BLoC (the package), get_it, and Riverpod placing in the top rankings (2, 3, 4, and 5, respectively). This is all fine until you look at the highest-ranked package, get (formally known as GetX), which, for me, is an anti-pattern and a no-go.
The second source is a bachelor’s thesis titled “State management approaches in Flutter” by Dmitrii Slepnev, published in December 2020.
In this thesis, Slepnev categorizes some of the most common state management techniques. He chooses six criteria, some of which are still subject to opinion, but they are well defined and easy to understand: complexity, amount of boilerplate code, code generation, time travel support, scalability, and testability.
An intuitive summary is provided in Table 11 of the study.
As you can see, this table makes comparing state management techniques easy, and it may help you to choose one based on such metrics. Likewise, after you choose your state management technique, this table could help you profile it.
Now that we have defined at least some metrics based on these two sources, let’s see how to apply them.
And the winner is…
Just like many of you at this point, I was initially confused and overwhelmed by all the possibilities and alternatives we have when it comes to state management in Flutter.
This is definitely a healthy sign, as it clearly indicates that Flutter is developing, but it could also lead to a negative downward spiral.
So, I looked into and tried some of the different options. I strongly recommend you try them out too and not take anyone’s opinion for granted, not even mine.
Try to implement and re-implement a sample app with different state management techniques, compare them, and see which one feels better to you and for what reasons. If possible, try to make the metrics explicit.
There is also a non-official code sample app that compares some of the alternatives.
For me, simplicity was the key, as simplicity is one of the most important values in my life.
After I tried some of them, I decided which one was the best for me: Provider/Riverpod. This choice was supported by Table 11 in Slepnev’s study. Since I prefer simplicity, I want the complexity to be as low as possible. Additionally, I want minimal boilerplate code, I don’t want any code generation, time travel is irrelevant to me, and scalability and testability must be decently good.
You can find a tutorial that will help you get started with Riverpod here
This decision is also confirmed by Mike’s picture, as I want a well-maintained package.
Last but not least, this choice is supported by the official Flutter docs: Simple state management is the only officially documented state management technique.
What is simple state management?
Simple state management, aka pragmatic state management, is based on Provider+ChangeNotifier. It’s well documented, and there’s a code sample.
In case you don’t know, Riverpod is a second take on Provider made by the same author, Remi Rousselet. You can think of Riverpod as a “Provider 2.0,” a better version of Provider. If you’re still using Provider, you should migrate to Riverpod, as it’s very straightforward.
So, you can easily implement simple state management nowadays with Riverpod and ChangeNotifier.
Note: ChangeNotifier, ValueNotifier (from SDK), and StateNotifier (from Remi) are all interchangeable. Since they all accomplish the same thing, you can use the one you prefer as long as you know what you’re doing and why.
Simple state management is very simple. It is built on three pillars:
- The state holder class encapsulates the application state. It’s responsible for changing the state and notifying about state changes; this is done with the ChangeNotifier class.
- The UI needs to have access to your state holder class (the ChangeNotifier); this is done through Riverpod with its provider classes (ChangeNotifierProvider, ValueNotifierProvider, StateNotifierProvider, etc.).
- The UI needs to consume the state changes; this is done through Riverpod with its consumer classes (Consumer, ConsumerWidget, ConsumerStatefulWidget).
This article is an extension of part of my talk for Flutter Vikings 2022.
In this article, I gave you some reasons why state management is a complex topic for Flutter and some metrics that can help you decide between the many options available. I recommend that you choose the safest bet (Riverpod) and the metric used (simplicity).
Now that you’ve chosen your state management technique, though, keep in mind that it’s not enough to create a scalable and robust app in a production environment. You’ll need a whole architecture.
Luckily, Riverpod is much more than a tool for state management; it’s a reactive caching and data-binding framework, and we can build the whole app architecture around Riverpod. Curious to know how?
I kept this article codeless on purpose, as I’ll follow up with a deep dive into architecture. So if you’re interested, keep an eye on this blog. I’ll keep you posted!
To be continued…
Alessio Salvadorini is a senior software engineer specializing in Flutter and currently working at Signant Health. The three most important F-words for Alessio are Flutter, Finland, and Fatherhood. You can find him on Twitter or read his thoughts on his blog, even though Twitter has been more lively lately.