State Management; probably the most controversial subject in the Flutter community. I remember when I started learning Flutter, Provider was the cool kid that everyone was recommending. Simple syntax, very detailed error messages, built on top of InheritedWidget
, it was even featured on the official flutter docs, why would you even choose something else? I mean they got the Approved By Google badge, that's probably the highest status a package could get in the community 🤡.
Then came Bloc, personally, I don't know which one came first but I started hearing about Bloc a bit later after Provider. Bloc was making use of Stream which was something I always avoided because I didn't know much about how to handle streams. Bloc was and still is awesome when it comes to building big scalable apps, I am not saying that you can't do that with Provider, but Bloc seemed to specifically target that kind of project and it provides many built-in tools for a seamless developer experience. Some would argue that there is too much boilerplate, well as long as it does the job and does it well, you can go for it.
Besides Provider and Bloc, many other packages came up with very interesting approaches to handle the billion-dollar problem which is, of course, State Management, we have Redux, MobX, Riverpod, Hooks and many others. But besides all those third-party packages, flutter has always had its own built-in tools to handle state management; and we are going to compare two of them: setState and ValueNotifier.
setState
This is the most basic approach to updating your local state. Basically, setState
is a method inside the State
class that rebuilds the widget; it takes a VoidCallback
as a parameter which is where you input all your variable updates, then after the widget is rebuilt, the changes you did in the VoidCallback
parameter are applied in the newly built widget. The only requirement is to use a StatefulWidget
because StatelessWidget
doesn't have a state; it's literally stateless
😏.
You might have come across setState
in the boilerplate code generated by the flutter create
command. It's not the best but it has its own advantages. Let's see an example of how to use it in a counter app.
import 'package:flutter/material.dart';
void main() {
runApp(const App());
}
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _value = 0;
@override
Widget build(BuildContext context) {
print('HOMEPAGE BUILT');
return Scaffold(
appBar: AppBar(title: const Text('Counter App')),
body: Center(child: Text('Count: $_value')),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() => _value++);
},
child: const Icon(Icons.add),
),
);
}
}
The problem with setState
The problem with this approach is that it rebuilds the whole widget just to apply the changes passed in the parameter. If you run the code above, you will see HOMEPAGE BUILT
printed in your console every time you press the FloatingActionButton
on your app.
This is not really a big problem when you have an app such as the one in our example, there is not much work to do there, just rendering a page with an app bar, a centered text, and a floating action button.
What if below our centered text, we had multiple animated widgets that rebuild every second, let's say we have 50 containers that change their shape every five seconds to look like random characters from the Sponge Bob cartoon.
Well, now it becomes complicated because whenever the FloatingActionButton
is pressed by the user, the whole HomePage
widget will rebuild as well as the 50 sponge bob characters; many frames will be dropped and our app will become very junky.
Introducing ValueNotifier (with Tim Cook's voice).
ValueNotifier
ValueNotifier
is a class that holds a single value and can update the said value, and notifies only its listeners of the changes. ValueNotifier
is often used with ValueListenableBuilder
which is a widget that listens to changes from a ValueNotifier
then rebuilds its child accordingly.
Kind of like the private story
feature on Instagram. setState
lets everyone know of your "story" but ValueNotifier
lets only your closest friends know about your story. And to add someone to your private story, you gotta wrap them in a ValueListenableBuilder
that listens to the same ValueNotifier
.
Let's remake our counter app with ValueNotifier
.
import 'package:flutter/material.dart';
void main() {
runApp(const App());
}
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final _counterNotifier = ValueNotifier<int>(0);
@override
Widget build(BuildContext context) {
print('HOMEPAGE BUILT');
return Scaffold(
appBar: AppBar(title: const Text('Counter App')),
body: Center(
child: ValueListenableBuilder(
valueListenable: _counterNotifier,
builder: (context, value, _) {
return Text('Count: $value');
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_counterNotifier.value++;
},
child: const Icon(Icons.add),
),
);
}
@override
void dispose() {
_counterNotifier.dispose();
super.dispose();
}
}
Now, whenever the counter value changes, only the Text
widget in the center of our page rebuilds. If you open your console, you will see that the HOMEPAGE BUILT
text is now printed only once even if you press that FloatingActionButton
a thousand times.
You might have noticed that we are overriding the dispose
method now. Well, that's because we want our ValueNotifier
to stay only inside the HomePage
and to disappear whenever the HomePage
widget is disposed.
ValueNotifier can also be used to manage the global state, but for that, we will have to create our own Custom notifier that extends the ValueNotifier class. We will talk about it maybe another time.
I hope this article demystified some doubts you had about setState
and ValueNotifier
.
Top comments (2)
Not too much of a Flutter genius, but the article is well crafted! Good work folk!
Well explained. Thank you!