DEV Community

Boris Kayi (銀髪)
Boris Kayi (銀髪)

Posted on • Updated on

State Managers : setState x ValueNotifier

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),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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.
Jojo's bizarre adventures: arrivederci

Discussion (1)

Collapse
joshuamur profile image
JoshuaMur

Not too much of a Flutter genius, but the article is well crafted! Good work folk!