ValueListenableBuilder in Flutter

Avoiding unnecessary rebuilding of widgets is always been of primary importance in Flutter. We have seen in our previous blog about bloc and Stream builders to avoid unnecessary rebuilds, there are many options in Flutter to tackle this scenario, and one of the easiest ways is using ValueListenableBuilders.

This widget has 3 properties

  1. valueListenable

As a StreamBuilder has a stream to which it listens, so ValueListenableBuilder has valueListenable which takes a listenable value which implements Listenable<T> (ValueNotifier and Animation) this listenable is the value to which our builder depends to rebuild itself.

2. builder

This is what that refreshes our UI whenever a new value is available.

3. child

This is our optimization property a widget that doesn't require to be rebuilt even if a new value is emitted by our listeners ( Same as we have encountered in our AnimatedBuilder which cache its child and avoid rebuilding).

We are going to use ValueNotifier as it implements ValueListenable<T> which matches our property no.1 valueListenable.

ValueNotifier<T>

ValueNotifier can be considered as a ChangeNotifier with a single value, it automatically notifies the listeners about the value has been changed and initiate a rebuild.

Now let’s see our experiment.

Here we have 3 changing value that is rebuilding with every OnPressed.

The values are -

Increment int

Decrement int and

Random Color

The idea here is to wrap the smallest part of our widget tree which should be rebuilt when the value is rebuilt so here we can see our first 3 containers only deals with changing the text however our fourth container has to deal with 3 changing values two integers and color so here we need to rebuild our container rather than above 3 containers where only text widget is to be rebuilt rather than the whole parent container.

So let’s get started.

First, we need to create our engine where all our logic resides.

Now we need three ValueNotifiers to carry each value.

ValueNotifier<int> incrementvalue = ValueNotifier(1);ValueNotifier<int> decrementValue = ValueNotifier(100);ValueNotifier<Color> color = ValueNotifier(Colors.black);}

NOTE: If Value Notifier is not initialized with the value it would be null and we need to take care of this in our widget tree.

Now we also need to dispose of these Notifiers so create an mth to dispose this.

incrementvalue.dispose();decrementValue.dispose();color.dispose();}

And now let’s add some mth’s

void decrement() {
decrementValue.value--;
}
void randomColor() {
color.value = Colors.primaries[Random().nextInt(Colors.primaries.length)];
}

moving to widget tree

We are going to use a stateful widget Simply as we need to dispose of some values.

initializing initState

void initState() {_engine = Engine();super.initState();}

Container Red

color: Colors.red,width: double.infinity,height: 100,child: Center(child: ValueListenableBuilder<int>(valueListenable: _engine.incrementvalue,builder: (context, value, _) {return Text('INC $value');})),),

Green Container

height: 100,color: Colors.green,child: Center(child: ValueListenableBuilder<int>(valueListenable: _engine.decrementValue,builder: (context, value, _) {return Text('DEC $value');})),),

Blue Container

height: 100,color: Colors.blue,child: Center(child: ValueListenableBuilder<int>(valueListenable: _engine.incrementvalue,builder: (context, value, _) {return ValueListenableBuilder<int>(valueListenable: _engine.decrementValue,builder: (context, value2, _) {return Text('INC : $value  / DEC : $value2');});})),),

Color Changing Container

 valueListenable: _engine.color,  builder: (context, color, _) => Container(     width: 200,      color: color,       child: ValueListenableBuilder<int>(              valueListenable: _engine.incrementvalue,              builder: (context, inc, _) =>                  ValueListenableBuilder(                       valueListenable: _engine.decrementValue,                       builder: (context, dec, _) => Column(                              mainAxisAlignment:                                    MainAxisAlignment.center,                                   children: [                                    Text('Increment $inc '),                                    SizedBox(                                    height: 50,                                ),                        Text('Decrement $dec '),               ],         ))),)),

Here we need to use nested ValueListenableBuilder as each value is independent of others to be built.

adding floating action buttons

mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [FloatingActionButton(onPressed: _engine.increment,child: const Text("INC"),),FloatingActionButton(onPressed: _engine.decrement,child: Text("DEC"),),FloatingActionButton(onPressed: _engine.randomColor,child: Text("COLOR"),),FloatingActionButton(onPressed: () {_engine.increment();_engine.decrement();_engine.randomColor();},child: Text("ALL"),)],));

and now time to dispose of our notifiers.

with this, we have covered one more method to avoid unnecessary rebuilds and separating UI with Business Logic.