53.1 ValueListenableBuilder
InheritedWidget
provides a way to share data from top to bottom within the widget tree, but there are many scenarios where the data flow is not strictly top-to-bottom, such as bottom-to-top or horizontal flows. To address this issue, Flutter offers a ValueListenableBuilder
component, which listens to a data source and rebuilds its builder when the data source changes. It is defined as follows:
const ValueListenableBuilder({ Key? key, required this.valueListenable, // Data source, type is ValueListenable<T> required this.builder, // Builder function this.child, })
valueListenable: This is of type
ValueListenable<T>
, indicating a listenable data source.builder: This function will be called to rebuild the child widget tree whenever the data source changes.
child: The builder rebuilds the entire child widget tree each time; if there are parts of the child widget tree that do not change, you can pass them to
child
. Thechild
will be passed as the third argument to the builder, allowing for component caching, similar to the third child inAnimatedBuilder
.
It can be noted that ValueListenableBuilder
is independent of data flow direction; it will rebuild the child widget tree whenever the data source changes, allowing for data sharing in any direction.
53.2 Example
Let’s implement a counter. When clicked, it will increase the count:
class ValueListenableRoute extends StatefulWidget { const ValueListenableRoute({Key? key}) : super(key: key); @override State<ValueListenableRoute> createState() => _ValueListenableState(); } class _ValueListenableState extends State<ValueListenableRoute> { // Define a ValueNotifier that will notify ValueListenableBuilder when the number changes final ValueNotifier<int> _counter = ValueNotifier<int>(0); static const double textScaleFactor = 1.5; @override Widget build(BuildContext context) { // Adding a + button will not trigger the entire ValueListenableRoute component's build print('build'); return Scaffold( appBar: AppBar(title: Text('ValueListenableBuilder Test')), body: Center( child: ValueListenableBuilder<int>( builder: (BuildContext context, int value, Widget? child) { // The builder method will only be called when _counter changes return Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ child!, Text('$value times', textScaleFactor: textScaleFactor), ], ); }, valueListenable: _counter, // Specifying the child property to cache the child component is useful when the child component does not depend on changing data and has a significant rendering cost child: const Text('Clicked ', textScaleFactor: textScaleFactor), ), ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.add), // When clicked, the value increases by 1, triggering ValueListenableBuilder to rebuild onPressed: () => _counter.value += 1, ), ); } }
After running the code, clicking the + button twice in succession produces the effect shown in Figure:
You can see that the functionality works as expected, and the console only printed "build" once when the page opened. Clicking the + button only caused ValueListenableBuilder
to rebuild the child widget tree, and the entire page did not rebuild. Therefore, the log panel only printed "build" once. Thus, a suggestion is to make ValueListenableBuilder
rebuild only the widgets that depend on the data source, which can reduce the scope of the rebuild. In other words, the granularity of ValueListenableBuilder
should be as fine as possible.
53.3 Summary
There are two key points to remember about ValueListenableBuilder
:
It is independent of data flow direction and can facilitate data sharing in any direction.
In practice, the granularity of
ValueListenableBuilder
should be as fine as possible to improve performance.