Flutter State Management: Riverpod, Bloc, Signals, GetX
Sandro Maglione
Get in touch with meMobile development
17 January 2024
•8 min read
Sandro Maglione
Mobile development
Which state management package should you use? In this post we compare riverpod
, bloc
, signals
, and get
to understand how they work and what are their pro and cons:
- What packages to install in
pubspec.yaml
- How to define state
- How to provide the state to a flutter app
- How to read and update the state
Required packages
Riverpod
Since riverpod uses code generation we need to add some dependencies required to annotate classes (riverpod_annotation
) and run code generation (riverpod_generator
and build_runner
).
It is also recommended to add riverpod_lint
to prevent common issues and simplify repetitive tasks:
Bloc
Bloc with flutter requires to add flutter_bloc
. If you want to use annotations (for example @immutable
) you also need to install meta
:
Signals
signals
is a single package, no need of anything else:
GetX
get
is also a single package:
Defining state
Riverpod
riverpod
uses code generation to define providers.
For simple state values you just need to annotate a function with @riverpod
:
This works the same if the function returns a Future
:
ref
allows you to access other providers (for dependency injection):
ref.watch
allows to listen and react to changes of the watched provider
For more complex values that change over time you need to:
- Define and annotate a
class
with@riverpod
- Add
extends _$NameOfTheClass
- Provide a
build
method used to initialize the provider state
Riverpod will generate different providers (
Provider
,FutureProvider
,StateNotifierProvider
).Using code generations allows to define all providers in the same way (
@riverpod
) regardless of their internal definition.
Bloc
Defining a full bloc requires 3 files:
- State definition (
_state
) - Events definition (
_event
) - Bloc implementation (
_bloc
)
Define a new folder for each bloc containing 3 files for state, events, and bloc.
Note: Events are not necessary if you use cubit instead of a full bloc
State is usually defined as a sealed class
, listing all the possible (finite) states:
State does not need to be a sealed class
, it can be any value that changes over time:
Events instead are always defined as sealed class
. They represent all possible actions that change the state:
Signals
A signal is defined by wrapping any value with signal
:
The signals package provides some other functions for specific values:
Future
(futureSignal
)List
(listSignal
)Set
(setSignal
)Map
(mapSignal
)Iterable
(iterableSignal
)Stream
(streamSignal
)
When a value is derived from another you instead use computed
:
GetX
Using get
you can turn any value into an observable by calling .obs
:
obs
wraps the value into aRx
that internally handles reactive state updates
For more complex states instead you create a class that extends GetxController
:
There is more.
Every week I build a new open source project, with a new language or library, and teach you how I did it, what I learned, and how you can do the same. Join me and other 600+ readers.
Using state with Flutter
Riverpod
Riverpod requires to wrap the entire app with ProviderScope
:
You then use a ConsumerWidget
to access ref
to watch
/read
providers:
Bloc
Bloc requires to define all blocs using a provider (BlocProvider
or RepositoryProvider
):
You can then use BlocBuilder
, BlocListener
, BlocConsumer
, context.read
, context.watch
to read the bloc state:
Signals
With signals listening to state changes requires wrapping a widget with Watch
.
You can then simply access any signal state using .value
:
GetX
GetX allows to listen to any observable value (.obs
) by wrapping an observable state with the Obx
widget:
For controller classes you can call Get.put
inside build
:
If you prefer to use widgets you can instead use GetBuilder
:
Update the state
Riverpod
You can modify the state with riverpod by updating the state
value inside a class
provider (StateNotifierProvider
).
Riverpod will react to the state change and update the widget state:
Bloc
Bloc uses events to trigger state changes.
A bloc provides an add
method where you pass an event:
Inside the bloc you define how to handle all events using on
.
You then call emit
with the updated state:
Signals
A signal can be updated by simply assigning its value:
Changing value
will trigger an update for the signal and all the values that depend on it (computed
).
GetX
A GetX controller can store some mutable state (var
).
You can update this state and call update
to trigger a UI change with the new value:
GetX will rebuild
GetBuilder
each timeupdate
is called
This is it!
You now have a complete overview of how each package works for all the most common requirements to manage the state of your flutter app.
You can now compare each solution and choose the most appropriate for your project 🤝
💡 Make sure to check out also all the extra features that each package offers (caching, routing, devtools, effects and more)
If you are interested to learn more, every week I publish a new open source project and share notes and lessons learned in my newsletter. You can subscribe here below 👇
Thanks for reading.