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
- valueListenable
As a StreamBuilder has a stream to which it listens, so ValueListenableBuilder has valueListenable which takes a listenable value that implements Listenable<T> (ValueNotifier and Animation) this listenable is the value to which our builder depends to rebuild itself.
2. builder
This is what 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 avoids 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.
class Engine{}
Now we need three ValueNotifiers to carry each value.
class Engine {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.
void dispose() {incrementvalue.dispose();decrementValue.dispose();color.dispose();}
And now let’s add some mth’s
void increment() {
incrementvalue.value = incrementvalue.value + 1;
}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
@overridevoid initState() {_engine = Engine();super.initState();}
Container Red
Container(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
Container(height: 100,color: Colors.green,child: Center(child: ValueListenableBuilder<int>(valueListenable: _engine.decrementValue,builder: (context, value, _) {return Text('DEC $value');})),),
Blue Container
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
ValueListenableBuilder<Color>( 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
floatingActionButton: Row(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.
@override
void dispose() {
_engine.dispose();
super.dispose();
}
with this, we have covered one more method to avoid unnecessary rebuilds and separating UI with Business Logic.
🎯Code.