diff --git a/examples/api/lib/widgets/value_listenable_builder/value_listenable_builder.0.dart b/examples/api/lib/widgets/value_listenable_builder/value_listenable_builder.0.dart new file mode 100644 index 00000000000000..1144cfed120829 --- /dev/null +++ b/examples/api/lib/widgets/value_listenable_builder/value_listenable_builder.0.dart @@ -0,0 +1,94 @@ +// 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/material.dart'; + +/// Flutter code sample for [ValueListenableBuilder]. + +void main() => runApp(const ValueListenableBuilderExampleApp()); + +class ValueListenableBuilderExampleApp extends StatelessWidget { + const ValueListenableBuilderExampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: ValueListenableBuilderExample(), + ); + } +} + +class ValueListenableBuilderExample extends StatefulWidget { + const ValueListenableBuilderExample({super.key}); + + @override + State createState() => _ValueListenableBuilderExampleState(); +} + +class _ValueListenableBuilderExampleState extends State { + final ValueNotifier _counter = ValueNotifier(0); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('ValueListenableBuilder Sample'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('You have pushed the button this many times:'), + ValueListenableBuilder( + builder: (BuildContext context, int value, Widget? child) { + // This builder will only get called when the _counter + // is updated. + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CountDisplay(count: 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: const Padding( + padding: EdgeInsets.all(10.0), + child: SizedBox( + width: 40, + height: 40, + child: FlutterLogo(size: 40), + ), + ), + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.plus_one), + onPressed: () => _counter.value += 1, + ), + ); + } +} + +class CountDisplay extends StatelessWidget { + const CountDisplay({super.key, required this.count}); + + final int count; + + @override + Widget build(BuildContext context) { + return Container( + width: 100, + height: 100, + padding: const EdgeInsetsDirectional.all(10), + child: Text('$count', style: Theme.of(context).textTheme.headline4), + ); + } +} diff --git a/examples/api/test/widgets/value_listenable_builder/value_listenable_builder.0_test.dart b/examples/api/test/widgets/value_listenable_builder/value_listenable_builder.0_test.dart new file mode 100644 index 00000000000000..b31e616230cccf --- /dev/null +++ b/examples/api/test/widgets/value_listenable_builder/value_listenable_builder.0_test.dart @@ -0,0 +1,35 @@ +// 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/material.dart'; +import 'package:flutter_api_samples/widgets/value_listenable_builder/value_listenable_builder.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Tapping FAB increments counter', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: example.ValueListenableBuilderExample(), + ), + ); + + String getCount() { + return (tester.widget( + find.descendant( + of: find.byType(example.CountDisplay), + matching: find.byType(Text), + ), + ) as Text).data!; + } + + expect(find.text('You have pushed the button this many times:'), findsOneWidget); + expect(find.text('0'), findsOneWidget); + expect(find.byIcon(Icons.plus_one), findsOneWidget); + expect(getCount(), equals('0')); + + await tester.tap(find.byType(FloatingActionButton).first); + await tester.pumpAndSettle(); + expect(getCount(), equals('1')); + }); +} diff --git a/packages/flutter/lib/src/widgets/tween_animation_builder.dart b/packages/flutter/lib/src/widgets/tween_animation_builder.dart index 3ef9f63fe4117e..226a348a605efd 100644 --- a/packages/flutter/lib/src/widgets/tween_animation_builder.dart +++ b/packages/flutter/lib/src/widgets/tween_animation_builder.dart @@ -90,6 +90,11 @@ import 'value_listenable_builder.dart'; /// [AnimatedBuilder], which can be used similarly to this /// [TweenAnimationBuilder], but unlike the latter it is powered by a /// developer-managed [AnimationController]. +/// +/// See also: +/// +/// * [ValueListenableBuilder], a widget whose content stays synced with a +/// [ValueListenable] instead of a [Tween]. class TweenAnimationBuilder extends ImplicitlyAnimatedWidget { /// Creates a [TweenAnimationBuilder]. /// diff --git a/packages/flutter/lib/src/widgets/value_listenable_builder.dart b/packages/flutter/lib/src/widgets/value_listenable_builder.dart index 0673fd2fd0c84a..56eadde7821b9c 100644 --- a/packages/flutter/lib/src/widgets/value_listenable_builder.dart +++ b/packages/flutter/lib/src/widgets/value_listenable_builder.dart @@ -40,63 +40,11 @@ typedef ValueWidgetBuilder = Widget Function(BuildContext context, T value, W /// Using this pre-built child is entirely optional, but can improve /// performance significantly in some cases and is therefore a good practice. /// -/// {@tool snippet} -/// +/// {@tool dartpad} /// 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 createState() => _MyHomePageState(); -/// } +/// setting state on the whole [Scaffold] in a counter app. /// -/// class _MyHomePageState extends State { -/// final ValueNotifier _counter = ValueNotifier(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: [ -/// const Text('You have pushed the button this many times:'), -/// ValueListenableBuilder( -/// builder: (BuildContext context, int value, Widget? child) { -/// // This builder will only get called when the _counter -/// // is updated. -/// return Row( -/// mainAxisAlignment: MainAxisAlignment.spaceEvenly, -/// children: [ -/// 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, -/// ), -/// ); -/// } -/// } -/// ``` +/// ** See code in examples/api/lib/widgets/value_listenable_builder/value_listenable_builder.0.dart ** /// {@end-tool} /// /// See also: @@ -108,6 +56,8 @@ typedef ValueWidgetBuilder = Widget Function(BuildContext context, T value, W /// you have a direct reference to. /// * [StreamBuilder], where a builder can depend on a [Stream] rather than /// a [ValueListenable] for more advanced use cases. +/// * [TweenAnimationBuilder], which can animate values in a widget based on a +/// [Tween]. class ValueListenableBuilder extends StatefulWidget { /// Creates a [ValueListenableBuilder]. ///