diff --git a/dev/bots/analyze_snippet_code.dart b/dev/bots/analyze_snippet_code.dart index b98b6ae0bc..d29d6d251e 100644 --- a/dev/bots/analyze_snippet_code.dart +++ b/dev/bots/analyze_snippet_code.dart @@ -458,8 +458,9 @@ class _SnippetChecker { } static const List ignoresDirectives = [ - '// ignore_for_file: duplicate_ignore', '// ignore_for_file: directives_ordering', + '// ignore_for_file: duplicate_ignore', + '// ignore_for_file: no_leading_underscores_for_local_identifiers', '// ignore_for_file: prefer_final_locals', '// ignore_for_file: unnecessary_import', '// ignore_for_file: unreachable_from_main', diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 3a432ee3a9..6c5c231a34 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -45,6 +45,7 @@ export 'package:flutter/rendering.dart' show RenderBox, RenderObject, debugDumpL // late Object? _myState, newValue; // int _counter = 0; // Future getApplicationDocumentsDirectory() async => Directory(''); +// late AnimationController animation; // An annotation used by test_analysis package to verify patterns are followed // that allow for tree-shaking of both fields and their initializers. This @@ -1092,9 +1093,73 @@ abstract class State with Diagnosticable { /// } /// ``` /// + /// Sometimes, the changed state is in some other object not owned by the + /// widget [State], but the widget nonetheless needs to be updated to react to + /// the new state. This is especially common with [Listenable]s, such as + /// [AnimationController]s. + /// + /// In such cases, it is good practice to leave a comment in the callback + /// passed to [setState] that explains what state changed: + /// + /// ```dart + /// void _update() { + /// setState(() { /* The animation changed. */ }); + /// } + /// //... + /// animation.addListener(_update); + /// ``` + /// /// It is an error to call this method after the framework calls [dispose]. /// You can determine whether it is legal to call this method by checking - /// whether the [mounted] property is true. + /// whether the [mounted] property is true. That said, it is better practice + /// to cancel whatever work might trigger the [setState] rather than merely + /// checking for [mounted] before calling [setState], as otherwise CPU cycles + /// will be wasted. + /// + /// ## Design discussion + /// + /// The original version of this API was a method called `markNeedsBuild`, for + /// consistency with [RenderObject.markNeedsLayout], + /// [RenderObject.markNeedsPaint], _et al_. + /// + /// However, early user testing of the Flutter framework revealed that people + /// would call `markNeedsBuild()` much more often than necessary. Essentially, + /// people used it like a good luck charm, any time they weren't sure if they + /// needed to call it, they would call it, just in case. + /// + /// Naturally, this led to performance issues in applications. + /// + /// When the API was changed to take a callback instead, this practice was + /// greatly reduced. One hypothesis is that prompting developers to actually + /// update their state in a callback caused developers to think more carefully + /// about what exactly was being updated, and thus improved their understanding + /// of the appropriate times to call the method. + /// + /// In practice, the [setState] method's implementation is trivial: it calls + /// the provided callback synchronously, then calls [Element.markNeedsBuild]. + /// + /// ## Performance considerations + /// + /// There is minimal _direct_ overhead to calling this function, and as it is + /// expected to be called at most once per frame, the overhead is irrelevant + /// anyway. Nonetheless, it is best to avoid calling this function redundantly + /// (e.g. in a tight loop), as it does involve creating a closure and calling + /// it. The method is idempotent, there is no benefit to calling it more than + /// once per [State] per frame. + /// + /// The _indirect_ cost of causing this function, however, is high: it causes + /// the widget to rebuild, possibly triggering rebuilds for the entire subtree + /// rooted at this widget, and further triggering a relayout and repaint of + /// the entire corresponding [RenderObject] subtree. + /// + /// For this reason, this method should only be called when the [build] method + /// will, as a result of whatever state change was detected, change its result + /// meaningfully. + /// + /// See also: + /// + /// * [StatefulWidget], the API documentation for which has a section on + /// performance considerations that are relevant here. @protected void setState(VoidCallback fn) { assert(() {