Which state management solution works best for you?

State management is a hot topic in the Flutter community it seems β so let’s have a go.
I have tried most of the existing state management solutions. Like bloc, Provider, Riverpod, Binder, etc. in search of a pragmatic solution; but do we really need third-party packages to handle state?
In my case, I’ve ended up using just a few core Flutter classes for handling state, that is: ValueNotifier
, ValueListenableBuilder
, AnimatedBuilder
and FutureBuilder
.
I’m happy using these few core classes; I’d rather be effective and understand the tools I work with than let complexity get in my way.
Basic state
For any type of state, I want to use across my app i use ValueNotifier
.
Note
ChangeNotifier
or otherListenable
types could work too. Also, if you have a very simple state useValueNotifier
directly without creating a new class.
I usually add one file per state I want to handle. Eg for the User
state I would add the file state/user_state.dart
. There i add a class UserState
which inherits from ValueNotifier
of type User
?
class UserState extends ValueNotifier<User?> {
UserState(User? user) : super(user);
}
In the same file, I declare a final variable userState
which i can now access anywhere in my app I want to listen for User
changes.
final userState = UserState(null);
Oh no, a global variable you say! Yes, you are right. Same as in Riverpod. You’ve got to keep your state objects somewhere and abstracting them more doesn’t necessarily make your code better. Also it is final so you can’t set it again, only it’s value.
Now each time you set userState.value
with a value that is not equal to the previous value, listeners are notified.
More complex state
You can add additional methods and logic to your state notifier in order to initialize and manipulate the state object.
In our case let’s initialize a FirebaseAuth stream which will update the user state on stream events.
class UserState extends ValueNotifier<User?> {
UserState(User? user) : super(user) {
FirebaseAuth.instance.userChanges().listen((User? user) {
value = user;
});
}
}
Listen for state changes
You can now listen for state changes using ValueListenableBuilder
anywhere in the widget tree:
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<User?>(
valueListenable: userState,
builder: (context, User? user, child) {
// TODO..
}
);
}
Or listen for changes in initState
by adding a listener to userState
:
userState.addListener(() {
User user? userState.value;
// TODO..
});
You can also listen for changes to
userState
in other state notifiers.
Listen for multiple state changes
If you want to listen for multiple state changes at once, you don’t want to nest a bunch of ValueListenableBuilder
objects, instead you can use AnimatedBuilder
and pass Listenable.merge
with a list of Listenables
(your state objects) to the animation
parameter.
return AnimatedBuilder(
animation: Listenable.merge([
userState,
someOtherState,
]),
builder: (context, child) {
final User? user = userState.value;
final OtherState? otherValue = someOtherState.value;
You could also use the
multi_value_listenable_builder
package (that I’ve contributed to). A minimal package that wraps anAnimatedBuilder
and returns a list of all the state values ββon changes.
Preload state
In case you’d want to preload some state at the beginning of your app, you could add a method to your state that returns a Future
after initialization.
class SomeState extend ValueNotifier {
Future<void> init() async {
// TODO do async work and return
}
}
Now you can use FutureBuilder
to initialize state objects by passing a list of futures with Future.wait
home: FutureBuilder(
future: Future.wait([
userState.init(),
otherState.init(),
]),
builder: (context, snapshot) {
// TODO ..
},
)
State metadata
If you’d like to know if your state is loading or have errors, you could wrap your data in a StateEvent
class based on internals of your state notifier.
class StateEvent<T> {
final T? data;
final bool loading;
final Object? error;
}
After looking into various state management solutions, I believe I’m happy with these simple building blocks for handling state. I feel I understand what is going on, and the solution is agile enough to handle most cases.
There are of course many ways to handle state in Flutter, and the best one is the one that works for you!
I hope this was easy to follow and that maybe you’ve learned something new!
Want to Connect?Please get in touch on Twitter @apptakk if you have any questions or comments