blob: 3495cfac8c9cefa6708f18ff5bae72f4b4b89f6e [file] [log] [blame]
// 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({Key? key, required this.title}) : super(key: key);
/// 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({
Key? key,
required this.valueListenable,
required this.builder,
this.child,
}) : assert(valueListenable != null),
assert(builder != null),
super(key: key);
/// 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) {
if (oldWidget.valueListenable != widget.valueListenable) {
oldWidget.valueListenable.removeListener(_valueChanged);
value = widget.valueListenable.value;
widget.valueListenable.addListener(_valueChanged);
}
super.didUpdateWidget(oldWidget);
}
@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);
}
}