| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter/foundation.dart'; |
| |
| import 'framework.dart'; |
| |
| /// Builds a [Widget] when given a concrete value of a [ValueListenable<T>]. |
| /// |
| /// If the `child` parameter provided to the [ValueListenableBuilder] is not |
| /// null, the same `child` widget is passed back to this [ValueWidgetBuilder] |
| /// and should typically be incorporated in the returned widget tree. |
| /// |
| /// See also: |
| /// |
| /// * [ValueListenableBuilder], a widget which invokes this builder each time |
| /// a [ValueListenable] changes value. |
| typedef ValueWidgetBuilder<T> = Widget Function(BuildContext context, T value, Widget? child); |
| |
| /// A widget whose content stays synced with a [ValueListenable]. |
| /// |
| /// Given a [ValueListenable<T>] and a [builder] which builds widgets from |
| /// concrete values of `T`, this class will automatically register itself as a |
| /// listener of the [ValueListenable] and call the [builder] with updated values |
| /// when the value changes. |
| /// |
| /// {@youtube 560 315 https://www.youtube.com/watch?v=s-ZG-jS5QHQ} |
| /// |
| /// ## Performance optimizations |
| /// |
| /// If your [builder] function contains a subtree that does not depend on the |
| /// value of the [ValueListenable], it's more efficient to build that subtree |
| /// once instead of rebuilding it on every animation tick. |
| /// |
| /// If you pass the pre-built subtree as the [child] parameter, the |
| /// [ValueListenableBuilder] will pass it back to your [builder] function so |
| /// that you can incorporate it into your build. |
| /// |
| /// Using this pre-built child is entirely optional, but can improve |
| /// performance significantly in some cases and is therefore a good practice. |
| /// |
| /// {@tool snippet} |
| /// |
| /// This sample shows how you could use a [ValueListenableBuilder] instead of |
| /// setting state on the whole [Scaffold] in the default `flutter create` app. |
| /// |
| /// ```dart |
| /// class MyHomePage extends StatefulWidget { |
| /// const MyHomePage({super.key, required this.title}); |
| /// final String title; |
| /// |
| /// @override |
| /// State<MyHomePage> createState() => _MyHomePageState(); |
| /// } |
| /// |
| /// class _MyHomePageState extends State<MyHomePage> { |
| /// final ValueNotifier<int> _counter = ValueNotifier<int>(0); |
| /// final Widget goodJob = const Text('Good job!'); |
| /// @override |
| /// Widget build(BuildContext context) { |
| /// return Scaffold( |
| /// appBar: AppBar( |
| /// title: Text(widget.title) |
| /// ), |
| /// body: Center( |
| /// child: Column( |
| /// mainAxisAlignment: MainAxisAlignment.center, |
| /// children: <Widget>[ |
| /// const Text('You have pushed the button this many times:'), |
| /// ValueListenableBuilder<int>( |
| /// builder: (BuildContext context, int value, Widget? child) { |
| /// // This builder will only get called when the _counter |
| /// // is updated. |
| /// return Row( |
| /// mainAxisAlignment: MainAxisAlignment.spaceEvenly, |
| /// children: <Widget>[ |
| /// Text('$value'), |
| /// child!, |
| /// ], |
| /// ); |
| /// }, |
| /// valueListenable: _counter, |
| /// // The child parameter is most helpful if the child is |
| /// // expensive to build and does not depend on the value from |
| /// // the notifier. |
| /// child: goodJob, |
| /// ) |
| /// ], |
| /// ), |
| /// ), |
| /// floatingActionButton: FloatingActionButton( |
| /// child: const Icon(Icons.plus_one), |
| /// onPressed: () => _counter.value += 1, |
| /// ), |
| /// ); |
| /// } |
| /// } |
| /// ``` |
| /// {@end-tool} |
| /// |
| /// See also: |
| /// |
| /// * [AnimatedBuilder], which also triggers rebuilds from a [Listenable] |
| /// without passing back a specific value from a [ValueListenable]. |
| /// * [NotificationListener], which lets you rebuild based on [Notification] |
| /// coming from its descendant widgets rather than a [ValueListenable] that |
| /// you have a direct reference to. |
| /// * [StreamBuilder], where a builder can depend on a [Stream] rather than |
| /// a [ValueListenable] for more advanced use cases. |
| class ValueListenableBuilder<T> extends StatefulWidget { |
| /// Creates a [ValueListenableBuilder]. |
| /// |
| /// The [valueListenable] and [builder] arguments must not be null. |
| /// The [child] is optional but is good practice to use if part of the widget |
| /// subtree does not depend on the value of the [valueListenable]. |
| const ValueListenableBuilder({ |
| super.key, |
| required this.valueListenable, |
| required this.builder, |
| this.child, |
| }) : assert(valueListenable != null), |
| assert(builder != null); |
| |
| /// The [ValueListenable] whose value you depend on in order to build. |
| /// |
| /// This widget does not ensure that the [ValueListenable]'s value is not |
| /// null, therefore your [builder] may need to handle null values. |
| /// |
| /// This [ValueListenable] itself must not be null. |
| final ValueListenable<T> valueListenable; |
| |
| /// A [ValueWidgetBuilder] which builds a widget depending on the |
| /// [valueListenable]'s value. |
| /// |
| /// Can incorporate a [valueListenable] value-independent widget subtree |
| /// from the [child] parameter into the returned widget tree. |
| /// |
| /// Must not be null. |
| final ValueWidgetBuilder<T> builder; |
| |
| /// A [valueListenable]-independent widget which is passed back to the [builder]. |
| /// |
| /// This argument is optional and can be null if the entire widget subtree |
| /// the [builder] builds depends on the value of the [valueListenable]. For |
| /// example, if the [valueListenable] is a [String] and the [builder] simply |
| /// returns a [Text] widget with the [String] value. |
| final Widget? child; |
| |
| @override |
| State<StatefulWidget> createState() => _ValueListenableBuilderState<T>(); |
| } |
| |
| class _ValueListenableBuilderState<T> extends State<ValueListenableBuilder<T>> { |
| late T value; |
| |
| @override |
| void initState() { |
| super.initState(); |
| value = widget.valueListenable.value; |
| widget.valueListenable.addListener(_valueChanged); |
| } |
| |
| @override |
| void didUpdateWidget(ValueListenableBuilder<T> oldWidget) { |
| super.didUpdateWidget(oldWidget); |
| if (oldWidget.valueListenable != widget.valueListenable) { |
| oldWidget.valueListenable.removeListener(_valueChanged); |
| value = widget.valueListenable.value; |
| widget.valueListenable.addListener(_valueChanged); |
| } |
| } |
| |
| @override |
| void dispose() { |
| widget.valueListenable.removeListener(_valueChanged); |
| super.dispose(); |
| } |
| |
| void _valueChanged() { |
| setState(() { value = widget.valueListenable.value; }); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return widget.builder(context, value, widget.child); |
| } |
| } |