My Journey of State Management Solutions in Flutter
A mini blog from as a response when someone asked on twitter what to choose between Riverpod and BLoC. Here is my journey
I have been developing Flutter apps since 2018, and I want to share my journey with different state management packages and approaches I’ve used so far.
TLDR;
I began with a simple setState, then switched to BLoC, Provider, Get_it, and finally Riverpod.
setState({})
I always recommend beginners to start with setState({})
before jumping into any state management solution. Because of this, setState({})
and InheritedWidget are by default state management solutions in the Flutter framework.
All other state management libraries use this under the hood with a nice and easy-to-use API or try to replicate state management solutions from other frameworks React.js and Vue.js on top of setState({})
and InheritedWidget.
But as your app grows, you start to see problems with setState({})
. A lot of business logic started to happen inside Widget and when you start putting business logic in the UI, you start to have bugs, stepping on each other code, breaking logic, etc.
BLoC
BLoC was the first state management solution I tried. It was familiar to me because I had used stream in RxJava in Android before.
BLoC uses streams to handle state changes. I started with one BLoC per page, but then I realized I needed some global blocs to share state across pages (i.e. global state).
That’s when I started to see some problems. It becomes hard to track where the global stream was updated, and sometimes a page would close the global stream mistakenly, causing crashes.
I didn’t use any third-party libraries for BLoC, just a basic BLoC class with an InheritedWidget to avoid passing the BLoC down the widget tree.
The downside was that I couldn’t use abstract blocs or the same type of BLoC with different configurations because of the type constraints in InheritedWidget.
Also, I found that BLoC required every action to be done through a stream, which made it difficult to debug the global blocs and a lot of boilerplate code
On the bright side, testing blocs was easy because they were pure Dart classes.
Provider
After BLoC, I moved to Provider. Provider also supports streams, but it has support for Notifiers, which are simpler to use. Provider had similar challenges as BLoC, but it had less boilerplate code for actions.
Testing providers required a widget tree, but I could still test the logic in a specific notifier, like in BLoC.
Get_it
Both BLoC and Provider are state management solutions, but they don’t have a good API for managing dependencies.
So I used get_it along with them to make dependency injection easier. Get_it allowed me to swap dependencies in tests easily.
There is also get_it_mixin package which allows you to manage the state in the widget, but personally, I did not find any usage compared to providers.
Custom Solution
Then I tried a custom solution, which was tailored to my app’s needs.
The good thing was that I didn’t have to deal with any package API. I could just add a method to our own custom library and move on with it.
But when I needed something that already existed in other packages, I had to reinvent the wheel and had to write a lot of boilerplate code, which sometimes missed some edge cases that other packages handled well.
Also, since it was a custom solution, it was hard to reuse it in other projects. It involved a lot of copying and pasting and fixing errors based on new models in a new project.
It was hard to set up.
Riverpod
Now I am using Riverpod, and I like it for several reasons compared to my previous experiences. First, the API is simple, similar to hooks in React and remember in Compose.
Second, the amount of boilerplate code is the same for either one state or multiple states, which was not the case with Provider and BLoC. For example, if I just want a flag to enable something, I need to write more lines of code in Provider and BLoC than in Riverpod.
Basic features like search, lazy loading, and provider dependencies require less code in Riverpod.
Third, dependency management, I removed get_it from my project once I started managing dependencies with Riverpod.
One thing I don’t like about Riverpod is that it doesn’t have an easy way to test providers separately. You need some framework code to test your providers independently. You can do it manually by injecting dependencies, but there is no built-in solution for that.
Note: Riverpod has code generation, which even allows you to write less code. However, I am not a fan of code generation because more code means more maintenance.
So, to conclude, there is NO Conclusion here. There is no one-size-fits-all solution. I am simply sharing my experience and thoughts.
Do Let me know your thoughts, what state management do you use? What are the challenges you faced in your journey? What are the things you like from other packages?
EDIT : Riverpod supports dart only testing.
That’s it, Folks
If want to you level up your flutter skills from beginner to Effective Pro developer then do check out my effectiveflutterdev.com live classes which I take on weekends. The new Batch 3 is to start in December.
If you enjoyed this blog, then would you be able to do me a quick favor and share this with your friends and colleagues? I'd really appreciate it and I think it could be valuable to them.
Thank you for being a part of the Widget Tricks Newsletter, and I am looking forward to hearing from you soon.
Your experience with state management is quite relatable, but I am still need TDD so I am not having those problems yet. But I think TDD is the way forward. What do you say about this and how much you cover this thing in your classes?