Flutter (53): rebuild on demand (ValueListenableBuilder)

Time: Column:Mobile & Frontend views:255

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. The child will be passed as the third argument to the builder, allowing for component caching, similar to the third child in AnimatedBuilder.

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:

Flutter (53): rebuild on demand (ValueListenableBuilder)

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:

  1. It is independent of data flow direction and can facilitate data sharing in any direction.

  2. In practice, the granularity of ValueListenableBuilder should be as fine as possible to improve performance.