To see the code for these examples, click here
If you are new to Flutter, or even if you've followed Flutter for a while, you have probably realized that there are many ways to deal with application state. I have no doubt that there will be a few new entries by the end of the month. Understanding the differences and trade-offs can be overwhelming since there aren't many examples that offer a direct comparison.
In my opinion, the best way to learn how to use a library is to build with it. The best way to compare two libraries is to do the same: build the same feature side-by-side to better understand the tradeoffs.
In this post, I'll walk thru the same application and demonstrate how to implement shared state using the same patterns across 5 different libraries from authors that I am fond of. These libraries range from popular to obscure and new to old.
I will do my best to identify the differences between the libraries and provide an opinion for each approach. To demonstrate each library's API, we'll implement a Notes app that displays an input field to create new notes and the list of created notes. You can play with a working demo here.
Before we start, I want to clarify that even though I might appear critical of some of these libraries, they still worth using. I find with all of these libraries you can accomplish most of what you need to and my opinion comes with a little subjective bias. I would have not included them otherwise. Likewise, a library's absence from this article is not an indictment. My opinions are not omniscient, and even if they were, I am lazy. Plus, this is a blog post after all, not a book 😊.
Getting Started
If you'd like to follow along, create a new Flutter app that to test each approach:
flutter create state_examples
To run the app at any time, execute the run
command in the project root.
flutter run
There are a few classes that we'll reuse across our examples, so let's define them next.
We'll also want to refactor our main.dart
file so that we can easily swap out pages.
Riverpod
Riverpod is a fairly new library created by Remi Rousselet as a direct response to some of the most common issues with his other library provider, specifically the ProviderNotFound
exception raised when developers fail to provide a dependency. In my opinion, that and its simpler API are its two biggest attractions.
When compared to other options, I would say that the mental hurdles are a slightly lower if you're just using StateNotifier since you're dealing with a simple API that implements the MVU pattern with tons of documentation. If you are exercising the full API, then I'd say the hurdle is much higher than the field.
Riverpod in action
To get started with Riverpod, install the library as a dependency:
dependencies:
# ...other dependencies
flutter_riverpod: ^0.12.1
riverpod: ^0.12.1
Next, add the ProviderScope
to the root / entry-point of the app:
Next, to create and mutate state we will create a NotesController
that extends a StateNotifier
and a StateNotifierProvider
to store a reference to NotesController
and provide it to our application and set some initial state:
Now we can use the read
extension method on BuildContext
and Consumer
to access the NotesController
anywhere in the app. The read
extension allows us to execute our mutation functions while the Consumer
widget allows us to subscribe to state changes. Here is the Notes UI implemented in Riverpod:
Finally, we'll uncomment home: SomePage()
and swap out SomePage with the widget we've defined above.
Verdict
This example may make Riverpod's usage seem trivial, but that is by design. Riverpod is an extremely powerful library that offers state management and dependency injection. That is not true for every entry on this list. But with great power comes great...never mind. The tradeoff is that power amounts to a fairly large API that might be intimidating. Riverpod is fairly well documented, but you might find yourself bouncing around to different locations depending on what you want to learn. Do not let that dissuade you from using it since most use cases can be solved just by using State Notifier.
Riverpod is extremely slick, and maybe a bit intimidating but it solves the major problems of provider. I do not use it currently, but if I started a project already familiar with provider and choosing between the two, I would choose Riverpod in a heartbeat.
Bloc
The Bloc library is the oldest entry on this list at the grand old age of 2! Originally created by Felix Angelov as an event-driven take on the Bloc pattern, it was recently re-architected to enable both event-driven and function-driven state updates. That may sound like a trivial change, but as someone who has worked with event-driven patterns across several frameworks, I can vouch for the efficacy of this change. I have seen the amount of boilerplate and technical debt that is incurred in large event-driven applications. Being able to choose when to use the pattern matters a lot.
Bloc has two types of state objects: Blocs
, which are event-driven and inherit fromCubits
, which are function-driven. Both inherit from Stream, and shield developers from some of its complexities. That is essentially where the differences end. Since Blocs
are an extension of Cubits
, the rest of the ecosystem (BlocProvider
, BlocListener
, BlocBuilder
, etc.) is shared by both of them.
Bloc also stands apart from every other library on this list as the gold standard for documentation. Its entire API is documented on bloclibrary.dev, along with examples for every major problem you might want to solve, from using RESTful/GraphQL/Firebase APIs, to handling navigation. The exceptionalism of their documentation cannot be overstated.
When compared to other options, I would say that the mental hurdles are lower since you're dealing with a simple API that implements the MVU pattern with tons of documentation.
Bloc in action
To get started with Bloc, install the library as a dependency:
dependencies:
# ...other dependencies
bloc: ^6.1.0
flutter_bloc: ^6.0.6
Next, to create and mutate state we will use a Cubit
from Bloc, initializing our class with some initial state and creating a couple of mutator functions:
We will use the BlocProvider
class to share our NotesCubit
across the app. For consistency, we will add BlocProvider
above App
in the widget tree.
Now we can use the bloc
extension method on BuildContext
and BlocBuilder
to access NotesCubit
in the app. Here is the Notes UI implemented in Bloc:
Again, we'll uncomment home: SomePage()
if we haven't already, and swap out SomePage with the widget we've defined above.
Verdict
I actually work on several projects that have migrated from provider to Bloc. That is not an indictment of Riverpod, because the refactoring is much simpler to Bloc than it is to Riverpod. Also, in cases where we were using Firebase, the fact that we were using Firebase made the refactoring straightforward since both Firebase and Bloc rely on Streams.
Currently, I find myself the most productive using Bloc because of its relatively small surface area and how well documented it is. Again, the care that has been given to making sure I understand how to use every bit of the API cannot be overstated.
I won't go as far as saying I'll be using Bloc for the next 5 years, but for me, it's definitely is the right fit for right now.
flutter_command
flutter_command is actually a reimagining of the rx_command library by Thomas Burkhart. The main difference between the two is that the former builds on ValueNotifier
and ValueListenableBuilder
while the latter is built on Streams. Both are derived from .Net's ReactiveCommands, so if you are familiar with that pattern maybe you'll find yourself at home with this library.
When compared to other options, I would say that setup is roughly the same and the mental hurdles are a bit higher if you've never worked with ReactiveCommands.
flutter_command in action
To get started with flutter_command, install the library as a dependency:
dependencies:
# ...other dependencies
flutter_command: ^0.9.3
Next, to create and mutate state we will create a NotesViewModel
class to define our state as well as the Command(s)
from flutter_command used to mutate that state:
Now we can use the NotesViewModel
we created to subscribe to state (using ValueListenableBuilder
) and execute our inputChangedCommand
and updateNotesCommand
to mutate our state. Here is the Notes UI implemented in flutter_command:
We'll uncomment home: SomePage()
if we haven't already, and swap out SomePage with the widget we've defined above.
Verdict
I have very mixed feelings about this library. For someone familiar with its concepts, it could be a perfect fit. For someone like me who is coming from the React/Angular community, it never felt like a natural fit. I found the documentation to be adequate for the sake of this exercise.
I would probably not use flutter_command, but I can see how anyone could be just as effective with this library as any other library on this list. I would definitely recommend it for people already familiar with the patterns or for people who want to experiment with something foreign and unique.
MobX
MobX.dart has been in the game for a while - almost 2 years! Anyone with a decent amount of React experience will immediately recognize MobX.dart as a port of MobX.js. The concepts are the same, minus the fact that you need to use code generation to mirror the magical feats performed under the hood at runtime by a dynamic language like JavaScript.
When compared to other options, I would say that setup is a bit higher than others and the mental hurdles are a bit higher if you have never worked with observables.
MobX in Action
To get started with MobX, install the library as a dependency:
dependencies:
# ...other dependencies
mobx: ^1.2.1+4
flutter_mobx: ^1.1.0+2
dev_dependencies:
build_runner: ^1.10.4
mobx_codegen: ^1.1.1+3
Next, to create and mutate state we will use a Store
from mobx, initializing our class with some initial state and creating a couple of mutator functions:
You may immediately notice red squiggles everywhere. This is because we haven't yet generated the files that our store is dependent upon. We'll do that by running the following command.
flutter packages pub run build_runner build
Then we can use the Observer
from flutter_mobx and the actions defined on the store to reference and update the state. Here is the Notes UI implemented in mobx:
Again, we'll uncomment home: SomePage()
if we haven't already, and swap out SomePage with the widget we've defined above.
Verdict
Although I have never used mobx in JavaScript, its concepts were immediately apparent to me since they are essentially the same. I like that it simplifies things even if I find the use of decorators + build generation a bit magical. I also personally feel that relying on build generation for the state management layer could be annoying. After the UI layer, this is the layer that I find myself changing the most.
The best things that mobx has going for it are its ease of use and great documentation. It is second only to Bloc on this list in that you should be able to learn everything you need from mobx.netlify.app.
binder
Fresh out of the kitchen, binder was created by Romain Rastel. It is heavily inspired by Recoil and Riverpod, seeking to create a library that is easier to learn and has a much smaller API surface area.
When compared to other options, I would say that the mental hurdles are significantly lower since you're dealing with a very small API that benefits from adequate documentation.
binder in Action
To get started with binder, install the library as a dependency:
dependencies:
# ...other dependencies
binder: ^0.1.5
Next, add the BinderScope to the root / entry-point of the app:
To create some state we will use a StateRef
from binder and set some initial state:
Next, to mutate the state we will create a Logic
class and a LogicRef
from binder:
Now we can use the read
, use
, and watch
extension methods on BuildContext to access the StateRef & LogicRef anywhere in the app. Here is the Notes UI implemented in binder:
Again, we'll uncomment home: SomePage()
if we haven't already, and swap out SomePage with the widget we've defined above.
Verdict
binder is new. Like, just-announced-a-week-before-this-post new. So expecting the same level of documentation as these other libraries would be unfair. For its current state, it is well documented. Also, because binder borrows heavily from provider and riverpod in an effort to be as close to Recoil as possible, it was extremely easy for me to pick up this library and run.
I have very high hopes for this library and I am looking forward to seeing more examples that highlight its benefits. If you take nothing else away, binder offers similar benefits to riverpod with a slimmed-down API surface area.
Conclusion
To be fair to each author, I want to restate that my preferences are subjective because they are based on my experience. Also, this is a basic example that doesn't include side-effects/asynchronicity or testing so it doesn't fully exercise the capabilities of these libraries.
Hopefully, this article has provided you with a basic understanding of how to accomplish the same task using each of these libraries and helped you identify their similarities and differences. Although I have my own preferences, I have no doubt that you can be effective with any entry on this list.
Prior art: React State 5 Ways