diff --git a/examples/api/README.md b/examples/api/README.md index 2c1a878295..07574a2b30 100644 --- a/examples/api/README.md +++ b/examples/api/README.md @@ -6,7 +6,7 @@ documentation in the framework. The examples can be run individually by just specifying the path to the example on the command line (or in the run configuration of an IDE). -For example (no pun intended!), to run, the first example from the `Curve2D` +For example (no pun intended!), to run the first example from the `Curve2D` class in Chrome, you would run it like so from the [api](.) directory: ``` diff --git a/examples/api/lib/foundation/change_notifier/change_notifier.0.dart b/examples/api/lib/foundation/change_notifier/change_notifier.0.dart new file mode 100644 index 0000000000..2801131833 --- /dev/null +++ b/examples/api/lib/foundation/change_notifier/change_notifier.0.dart @@ -0,0 +1,65 @@ +// 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. + +// Flutter code sample for ChangeNotifier with an AnimatedBuilder + +import 'package:flutter/material.dart'; + +class CounterBody extends StatelessWidget { + const CounterBody({Key? key, required this.counterValueNotifier}) : super(key: key); + + final ValueNotifier counterValueNotifier; + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Current counter value:'), + // Thanks to the [AnimatedBuilder], only the widget displaying the + // current count is rebuilt when `counterValueNotifier` notifies its + // listeners. The [Text] widget above and [CounterBody] itself aren't + // rebuilt. + AnimatedBuilder( + // [AnimatedBuilder] accepts any [Listenable] subtype. + animation: counterValueNotifier, + builder: (BuildContext context, Widget? child) { + return Text('${counterValueNotifier.value}'); + }, + ), + ], + ), + ); + } +} + +class MyApp extends StatefulWidget { + const MyApp({Key? key}) : super(key: key); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + final ValueNotifier _counter = ValueNotifier(0); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('AnimatedBuilder example')), + body: CounterBody(counterValueNotifier: _counter), + floatingActionButton: FloatingActionButton( + onPressed: () => _counter.value++, + child: const Icon(Icons.add), + ), + ), + ); + } +} + +void main() { + runApp(const MyApp()); +} diff --git a/examples/api/test/foundation/change_notifier/change_notifier.0_test.dart b/examples/api/test/foundation/change_notifier/change_notifier.0_test.dart new file mode 100644 index 0000000000..c7fc76c6cb --- /dev/null +++ b/examples/api/test/foundation/change_notifier/change_notifier.0_test.dart @@ -0,0 +1,33 @@ +// 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/foundation/change_notifier/change_notifier.0.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Smoke test for MyApp', (WidgetTester tester) async { + await tester.pumpWidget(const MyApp()); + + expect(find.byType(Scaffold), findsOneWidget); + expect(find.byType(CounterBody), findsOneWidget); + expect(find.byType(FloatingActionButton), findsOneWidget); + expect(find.text('Current counter value:'), findsOneWidget); + }); + + testWidgets('Counter update', (WidgetTester tester) async { + await tester.pumpWidget(const MyApp()); + + // Initial state of the counter + expect(find.text('0'), findsOneWidget); + + // Tapping the increase button + await tester.tap(find.byType(FloatingActionButton)); + await tester.pumpAndSettle(); + + // Counter should be at 1 + expect(find.text('1'), findsOneWidget); + expect(find.text('0'), findsNothing); + }); +} diff --git a/packages/flutter/lib/src/foundation/change_notifier.dart b/packages/flutter/lib/src/foundation/change_notifier.dart index d3b521f7d3..a9e20eda14 100644 --- a/packages/flutter/lib/src/foundation/change_notifier.dart +++ b/packages/flutter/lib/src/foundation/change_notifier.dart @@ -98,6 +98,8 @@ abstract class ValueListenable extends Listenable { /// It is O(1) for adding listeners and O(N) for removing listeners and dispatching /// notifications (where N is the number of listeners). /// +/// {@macro flutter.flutter.animatedbuilder_changenotifier.rebuild} +/// /// See also: /// /// * [ValueNotifier], which is a [ChangeNotifier] that wraps a single value. diff --git a/packages/flutter/lib/src/widgets/transitions.dart b/packages/flutter/lib/src/widgets/transitions.dart index 75742a191c..eba5269118 100644 --- a/packages/flutter/lib/src/widgets/transitions.dart +++ b/packages/flutter/lib/src/widgets/transitions.dart @@ -979,7 +979,7 @@ class DefaultTextStyleTransition extends AnimatedWidget { /// rebuilding it on every animation tick. /// /// If you pass the pre-built subtree as the [child] parameter, the -/// AnimatedBuilder will pass it back to your builder function so that you +/// [AnimatedBuilder] 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 @@ -993,6 +993,25 @@ class DefaultTextStyleTransition extends AnimatedWidget { /// ** See code in examples/api/lib/widgets/transitions/animated_builder.0.dart ** /// {@end-tool} /// +/// {@template flutter.flutter.animatedbuilder_changenotifier.rebuild} +/// ## Improve rebuilds performance using AnimatedBuilder +/// +/// Despite the name, [AnimatedBuilder] is not limited to [Animation]s. Any subtype +/// of [Listenable] (such as [ChangeNotifier] and [ValueNotifier]) can be used with +/// an [AnimatedBuilder] to rebuild only certain parts of a widget when the +/// [Listenable] notifies its listeners. This technique is a performance improvement +/// that allows rebuilding only specific widgets leaving others untouched. +/// +/// {@tool dartpad} +/// The following example implements a simple counter that utilizes an +/// [AnimatedBuilder] to limit rebuilds to only the [Text] widget. The current count +/// is stored in a [ValueNotifier], which rebuilds the [AnimatedBuilder]'s contents +/// when its value is changed. +/// +/// ** See code in examples/api/lib/foundation/change_notifier/change_notifier.0.dart ** +/// {@end-tool} +/// {@endtemplate} +/// /// See also: /// /// * [TweenAnimationBuilder], which animates a property to a target value