This week is a deep dive in everything about State management.
I tried 4 different packages (riverpod
, bloc
, signals
,Β get
) to understand how they all work and what solution they propose.
π Next week project π Best state management package in @FlutterDev? π€ Let's try them all and see! π₯ I'll implement the same app with multiple packages π€ riverpod 𧱠bloc π± getx π signals You donβt want to miss this π sandromaglione.com/newsletter?refβ¦
Here is everything that I learned π
Tech stack
- Flutter: I explored state management for flutter apps, but the principles of how state management works apply to every frontend framework π
Setup
The project is a normal Flutter app. I created a separate folder and main.dart
for each state management package:
Each folder is specific for each state management package, and each has its own entry file main.dart
In this way I can run each main.dart
to test the app for every package. The apps look the same, but they all implement a different state management solution.
Compose words using the letters in the grid. The app needs to manage the state for the current selection, the words dictionary, the found words, and the points π€
Let's see how this all works!
Get started
What is state management really? π€
State Management is the process of dealing with state changes over time: store initial value, read current value, update value
Every time some state changes the UI should rerender.
In summary:
- Store a variable somewhere (State)
- Access the current value of the variable (UI)
- Update the variable (Events)
- π Reflect these changes in the UI
The last point is tricky π€
The update should involve the smallest subset of UI possible to avoid rerendering the full screen after every state change π€―
This is where a state management library comes to the rescue π
There is more π€©
Timeless coding principles, practices, and tools that make a difference, regardless of your language or framework, delivered in your inbox every week.
Implementation
First problem: where is the state stored? In what format?
A common practice is to make the state immutable:
Immutable state allows the state management library to track changes over time. Just compare the previous value with the updated one: if the value is different, rerender the UI
This is the approach used by riverpod, bloc, and signals:
part of 'dictionary_bloc.dart';
@immutable /// π Make the state immutable
sealed class DictionaryState {
const DictionaryState();
}
When the state is not immutable you need a way to notify the UI that something changed. GetX requires you to manually call an update
method:
void onPanStart(GridSettings gridSettings, DragStartDetails details) {
final pos = _panIndex(gridSettings, details.localPosition);
gesture = gesture.add(pos.index); /// π State changed
update(); /// π Manually notify the UI
}
Surgical Renderingβ’οΈ
Second problem: how do you access the state? Which part of the UI should be rerendered?
A common solution in Flutter is providing a widget that tracks state changes and rerenders itself after every update
This is how providers and builders work:
class Grid extends StatelessWidget {
const Grid({super.key});
@override
Widget build(BuildContext context) {
/// π [Watch] widget listens for changes and rerenders on every update
return Watch(
(context) {
final dictionaryAsync = dictionary.value;
Another approach is to read the state directly from context (ref.watch
). This method will rerender the full widget after every update (and not just a subset of the tree):
class Grid extends StatelessWidget {
const Grid({super.key});
@override
Widget build(BuildContext context) {
/// Watch values and rerender the full [Grid] after every update
final gestureBloc = context.read<GestureBloc>();
final gestureBlocState = context.watch<GestureBloc>().state;
final gridSettings = context.watch<GridSettings>();
final boardBloc = context.watch<BoardBloc>();
return GridLayout(
Update events
Is the state allowed to change from any value to any other value? π€
It is common for a UI to be modelled as a state machine:
I created a state management library π₯ XState in dart is possible, and it's here π β Context + State β Actions β Events Transitions are allowed only from certain states to others: State Machines β¨
No one of the package I tried implement this pattern ππΌββοΈ
part 'gesture_provider.g.dart';
@riverpod
class GestureNotifier extends _$GestureNotifier {
@override
Gesture build() => Gesture.empty();
void onPanStart(GridSettings gridSettings, DragStartDetails details) {
final pos = _panIndex(gridSettings, details.localPosition);
state = state.add(pos.index); /// π No limits on how can you update the state
}
Pay attention when you update the state: make sure the event is allowed and the data is valid
Every state management solution has its own tradeoffs, which one to choose mostly depends on your project π
I wrote a complete guide on state management in Flutter using riverpod, bloc, signals, and GetX.
This guide covers everything that you need to know about state management with each package and how they work in practice π
Takeaways
- State management is about storing, reading, and updating data
- State management libraries are responsible to update a subset of the UI after each state change
- There is no "best" solution: each package has its own patterns and features (caching, devtools, immutability)
- Since the problem solved is the same, your app will work regardless of the package that you choose, so go ahead and pick your favorite π€
π Next week project π Best state management package in @FlutterDev? π€ Let's try them all and see! π₯ I'll implement the same app with multiple packages π€ riverpod 𧱠bloc π± getx π signals You donβt want to miss this π sandromaglione.com/newsletter?refβ¦
State management is an hard problem. Knowing the principles and the most common solutions helps when working on your app.
Make sure to try many libraries and choose the best tool for the job π οΈ
See you next π