There are 3 hard things in programming: naming things, cache invalidation and selecting a state management approach in Flutter. If youāve been a part of the online Flutter community even for a little while, you know that one of the most notorious and highly discussed topics is state management. Most of the times I stay away from these discussions, apart from an ironic tweet (xweet?) here and there. But today I want to talk about it a little. Just a brief (as brief as it comes to my writings š) note on what I use in my projects, why I use that, and what you should use in yours š
I came to Flutter straight from Native Android, building apps for clients in an outsourcing agency. Many different apps for many different clients. Conceptually, we were solving the same problems there, since we were building, well, apps. Today there is a whole guide in the Android documentation regarding state management, app architecture, dependency injection and everything. But it wasnāt always so, and for years there were constant new recommendations which made some developers confused, other developers happy. We went from custom made MVP architectures, to ViewModels from the Android Architecture Components backed by LiveData, then by RxJava, then by Kotlin Flows. If you started a project a year later than the previous one, you could be almost certain that you would now be using a different (even if slightly) approach to state management.
During those turbulent times, in my company we discovered a somewhat exotic (for Native Android), but an interesting approach used by the Freeletics team: their RxRedux library. I was young and easily impressed and despite the initial complexity, fell in love with this approach. It brought with it quite some boilerplate and unconventional solutions, along with (for me) complete mental clarity and predictability.
I donāt remember all of the details, but in a nutshell you supplied an Action
to the reducer
, which then mapped it to new State
. This would happen in the ViewModel
, and in your View
(which was an Activity
or Fragment
) you would subscribe to that single State
of the ViewModel
and react to changes. As declarative as it could be in the realm of imperative Android Views. This clash of declarative & imperative was where most of the problems happened, since we had to map the ViewModel
state to the View
state via data binding. Wow, Android was complex. No wonder I love Flutter so much š Anyway, I think this was what set the foundation for my own preferences of managing state going forward.
We picked up Flutter in 2019, and the initial app architecture, along with the state management solution was selected by our then tech lead. And it was BLoC, specifically the flutter_bloc
library. While the Bloc pattern varies a lot in details from the Redux implementation that we used in Android, it was very easy to map to our existing mental model: supply events, map them to state, subscribe to state changes and rebuild reactively. Honestly, never did we feel a need to switch to a different solution or tool, although there were limitations to which we found solutions. And I use it in my personal projects all of the time, itās a no brainer. As you can see, there are at least 2 very important factors for a tool (or approach selection): previous experience and specific team preference. After all, youāre working in a team, and one of the most important things is that you use what makes you productive. Bloc has been many times blamed for verbosity and boilerplate. I see where it comes from, I partially agree. But frankly, Iād choose boilerplate for the sake of predictability over magic any time.
Besides team preferences, here is some criteria I personally care about when it comes to state management approach:
Easy to scale. Yes, yes, Iāve heard that scalability for our 10-user-JSON-query-button-coloring app is overrated. I prefer to be an optimist and look forward to my 10 user app to become a 100k user app, without me needing to refactor the whole thing upfront. Of course, refactoring in some form is inevitable, and over-engineering is a real threat, but completely ignoring architecture practices, especially when you have experience and donāt spend a lot of time implementing them, also isnāt pragmatic. So I use cubits for something small, usually 2-3 methods. If itās more than that, I refactor to bloc and good to go.
Predictability. When I implement a feature, I want to be 100% sure where to put UI, where to put business logic, how to do the mapping, and so on. Predictability ensures productivity, and thatās the end goal, isnāt it?
If itās a library, then how does it work internally. Now, in no way Iām an expert in all of the details of
flutter_bloc
, but I do understand enough to be sure that I can debug any issues that arise.Compliment the framework. I want the pattern (and the tool) that I select to blend organically into the framework. Now, this is controversial in regards to me using
RxRedux
š But even though it was quite unconventional, I still feel like it fit in quite naturally, especially whenRxJava
was king. And in terms of bloc, its stream based architecture and Flutter declarative approach blend really well.Solve one problem and solve it well. We have seen bad examples of libraries turning into micro-frameworks and hence Iām alerted when I see a library do many things at once, since there is a risk of me highly depending on it in the future, and if itās abandoned, or at some point I understand that I donāt want to work with it, Iām stuck with refactoring more than Iād like.
Now, this is only my criteria. It is based on my experience, on my personal preference. Does it coincide with your criteria or preferences? I donāt know. It can be different. Things that are important to me, might not be important to you. And vice versa. And thatās fine. Thatās why I think that it is important to establish your own criteria before listening to anyoneās opinions. One thing that I dislike in such highly debatable topics is when people say there is only one correct way to do it, or one correct tool to use. This can be very confusing, especially when such claims are made about a variety of tools (or patterns).
So, as I said, my criteria is backed by my experience, so itās practical. Practice is the only way to find out what works and doesnāt work for you. If I would come to Flutter right now, this would be the order in which Iād approach state management topics:
Understand the scope and lifetime of the possible state. Some is more persistent than other, and is also accessible in more than one place. In Flutter this distinction is commonly known as āEphemeral vs Appā state.
Obviously, start with the good old
setState
. Very quickly run into limitations, start forming your criteria. Learn about the ālifting state upā pattern. Although this specific tutorial suggests usingprovider
, Iād say to try and experiment withInheritedWidget
, especially if youāre not running anywhere and have time to learn. Maybe my article aboutBuildContext
will give you more insight into the topic, although itās not about state management.Explore the tools available out of the box: again,
InheritedWidget
,InheritedModel
,InheritedNotifier
and implementations ofListenable
, specificallyValueNotifier
andChangeNotifier
. I think at this point you will also run into working with DartStreams
. There is quite some complexity involved here, but I believe understanding and experimenting with it will give you invaluable insights.At this point, several things can happen. Maybe you have built a working state management solution with just those tools and you donāt even need libraries! Totally possible. If not, then you definitely have a list of pain points that you want a tool to solve with. Now you can start experimenting with 3rd party libraries, see what works and doesnāt work for you, and make that decision for yourself. I guarantee this decision will vary from person to person š
And one more thing, tools ā patterns. Well, sometimes they are, but not always. Same patterns can be implemented with various tools. I believe learning about patterns is beneficial and can aid a lot when selecting a tool.
This newsletter comes as a surprise for me as it does for you š I really didnāt plan to write it, but it just kinda happened. Now you know my opinion on this highly debated topic. Not sure if you needed it, since there are plenty out there, but oh well š
Just a couple of links for you to check out:
Architectural thinking on Flutter state management by Majid Hajian
My article on how to implement Segmented State Pattern with
flutter_bloc
Tutorials on the Bloc documentation website. Theyāre great!
P.S. While Iām here, I wanted to share that Iāll be speaking at DevFest Stockholm this Saturday, with Mangirdas Kazlauskas, so catch us there!
And letās connect on Twitter (aka X) :)
-Daria š