1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/foundation.dart';
6
7import 'framework.dart';
8
9/// Builds a [Widget] when given a concrete value of a [ValueListenable<T>].
10///
11/// If the `child` parameter provided to the [ValueListenableBuilder] is not
12/// null, the same `child` widget is passed back to this [ValueWidgetBuilder]
13/// and should typically be incorporated in the returned widget tree.
14///
15/// See also:
16///
17/// * [ValueListenableBuilder], a widget which invokes this builder each time
18/// a [ValueListenable] changes value.
19typedef ValueWidgetBuilder<T> = Widget Function(BuildContext context, T value, Widget? child);
20
21/// A widget whose content stays synced with a [ValueListenable].
22///
23/// Given a [ValueListenable<T>] and a [builder] which builds widgets from
24/// concrete values of `T`, this class will automatically register itself as a
25/// listener of the [ValueListenable] and call the [builder] with updated values
26/// when the value changes.
27///
28/// {@youtube 560 315 https://www.youtube.com/watch?v=s-ZG-jS5QHQ}
29///
30/// ## Performance optimizations
31///
32/// If your [builder] function contains a subtree that does not depend on the
33/// value of the [ValueListenable], it's more efficient to build that subtree
34/// once instead of rebuilding it on every animation tick.
35///
36/// If you pass the pre-built subtree as the [child] parameter, the
37/// [ValueListenableBuilder] will pass it back to your [builder] function so
38/// that you can incorporate it into your build.
39///
40/// Using this pre-built child is entirely optional, but can improve
41/// performance significantly in some cases and is therefore a good practice.
42///
43/// {@tool snippet}
44///
45/// This sample shows how you could use a [ValueListenableBuilder] instead of
46/// setting state on the whole [Scaffold] in the default `flutter create` app.
47///
48/// ```dart
49/// class MyHomePage extends StatefulWidget {
50/// const MyHomePage({super.key, required this.title});
51/// final String title;
52///
53/// @override
54/// State<MyHomePage> createState() => _MyHomePageState();
55/// }
56///
57/// class _MyHomePageState extends State<MyHomePage> {
58/// final ValueNotifier<int> _counter = ValueNotifier<int>(0);
59/// final Widget goodJob = const Text('Good job!');
60/// @override
61/// Widget build(BuildContext context) {
62/// return Scaffold(
63/// appBar: AppBar(
64/// title: Text(widget.title)
65/// ),
66/// body: Center(
67/// child: Column(
68/// mainAxisAlignment: MainAxisAlignment.center,
69/// children: <Widget>[
70/// const Text('You have pushed the button this many times:'),
71/// ValueListenableBuilder<int>(
72/// builder: (BuildContext context, int value, Widget? child) {
73/// // This builder will only get called when the _counter
74/// // is updated.
75/// return Row(
76/// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
77/// children: <Widget>[
78/// Text('$value'),
79/// child!,
80/// ],
81/// );
82/// },
83/// valueListenable: _counter,
84/// // The child parameter is most helpful if the child is
85/// // expensive to build and does not depend on the value from
86/// // the notifier.
87/// child: goodJob,
88/// )
89/// ],
90/// ),
91/// ),
92/// floatingActionButton: FloatingActionButton(
93/// child: const Icon(Icons.plus_one),
94/// onPressed: () => _counter.value += 1,
95/// ),
96/// );
97/// }
98/// }
99/// ```
100/// {@end-tool}
101///
102/// See also:
103///
104/// * [AnimatedBuilder], which also triggers rebuilds from a [Listenable]
105/// without passing back a specific value from a [ValueListenable].
106/// * [NotificationListener], which lets you rebuild based on [Notification]
107/// coming from its descendant widgets rather than a [ValueListenable] that
108/// you have a direct reference to.
109/// * [StreamBuilder], where a builder can depend on a [Stream] rather than
110/// a [ValueListenable] for more advanced use cases.
111class ValueListenableBuilder<T> extends StatefulWidget {
112 /// Creates a [ValueListenableBuilder].
113 ///
114 /// The [child] is optional but is good practice to use if part of the widget
115 /// subtree does not depend on the value of the [valueListenable].
116 const ValueListenableBuilder({
117 super.key,
118 required this.valueListenable,
119 required this.builder,
120 this.child,
121 });
122
123 /// The [ValueListenable] whose value you depend on in order to build.
124 ///
125 /// This widget does not ensure that the [ValueListenable]'s value is not
126 /// null, therefore your [builder] may need to handle null values.
127 final ValueListenable<T> valueListenable;
128
129 /// A [ValueWidgetBuilder] which builds a widget depending on the
130 /// [valueListenable]'s value.
131 ///
132 /// Can incorporate a [valueListenable] value-independent widget subtree
133 /// from the [child] parameter into the returned widget tree.
134 final ValueWidgetBuilder<T> builder;
135
136 /// A [valueListenable]-independent widget which is passed back to the [builder].
137 ///
138 /// This argument is optional and can be null if the entire widget subtree the
139 /// [builder] builds depends on the value of the [valueListenable]. For
140 /// example, in the case where the [valueListenable] is a [String] and the
141 /// [builder] returns a [Text] widget with the current [String] value, there
142 /// would be no useful [child].
143 final Widget? child;
144
145 @override
146 State<StatefulWidget> createState() => _ValueListenableBuilderState<T>();
147}
148
149class _ValueListenableBuilderState<T> extends State<ValueListenableBuilder<T>> {
150 late T value;
151
152 @override
153 void initState() {
154 super.initState();
155 value = widget.valueListenable.value;
156 widget.valueListenable.addListener(_valueChanged);
157 }
158
159 @override
160 void didUpdateWidget(ValueListenableBuilder<T> oldWidget) {
161 super.didUpdateWidget(oldWidget);
162 if (oldWidget.valueListenable != widget.valueListenable) {
163 oldWidget.valueListenable.removeListener(_valueChanged);
164 value = widget.valueListenable.value;
165 widget.valueListenable.addListener(_valueChanged);
166 }
167 }
168
169 @override
170 void dispose() {
171 widget.valueListenable.removeListener(_valueChanged);
172 super.dispose();
173 }
174
175 void _valueChanged() {
176 setState(() { value = widget.valueListenable.value; });
177 }
178
179 @override
180 Widget build(BuildContext context) {
181 return widget.builder(context, value, widget.child);
182 }
183}
184