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. Thechildwill 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
ValueListenableBuildershould be as fine as possible to improve performance.