| // 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 'dart:collection'; |
| import 'dart:developer' show Timeline; // to disambiguate reference in dartdocs below |
| |
| import 'package:flutter/foundation.dart'; |
| |
| import 'basic.dart'; |
| import 'framework.dart'; |
| import 'localizations.dart'; |
| import 'media_query.dart'; |
| import 'overlay.dart'; |
| import 'table.dart'; |
| |
| // Examples can assume: |
| // late BuildContext context; |
| // List<Widget> children = <Widget>[]; |
| // List<Widget> items = <Widget>[]; |
| |
| // Any changes to this file should be reflected in the debugAssertAllWidgetVarsUnset() |
| // function below. |
| |
| /// Log the dirty widgets that are built each frame. |
| /// |
| /// Combined with [debugPrintBuildScope] or [debugPrintBeginFrameBanner], this |
| /// allows you to distinguish builds triggered by the initial mounting of a |
| /// widget tree (e.g. in a call to [runApp]) from the regular builds triggered |
| /// by the pipeline. |
| /// |
| /// Combined with [debugPrintScheduleBuildForStacks], this lets you watch a |
| /// widget's dirty/clean lifecycle. |
| /// |
| /// To get similar information but showing it on the timeline available from the |
| /// Observatory rather than getting it in the console (where it can be |
| /// overwhelming), consider [debugProfileBuildsEnabled]. |
| /// |
| /// See also: |
| /// |
| /// * [WidgetsBinding.drawFrame], which pumps the build and rendering pipeline |
| /// to generate a frame. |
| bool debugPrintRebuildDirtyWidgets = false; |
| |
| /// Signature for [debugOnRebuildDirtyWidget] implementations. |
| typedef RebuildDirtyWidgetCallback = void Function(Element e, bool builtOnce); |
| |
| /// Callback invoked for every dirty widget built each frame. |
| /// |
| /// This callback is only invoked in debug builds. |
| /// |
| /// See also: |
| /// |
| /// * [debugPrintRebuildDirtyWidgets], which does something similar but logs |
| /// to the console instead of invoking a callback. |
| /// * [debugOnProfilePaint], which does something similar for [RenderObject] |
| /// painting. |
| /// * [WidgetInspectorService], which uses the [debugOnRebuildDirtyWidget] |
| /// callback to generate aggregate profile statistics describing which widget |
| /// rebuilds occurred when the |
| /// `ext.flutter.inspector.trackRebuildDirtyWidgets` service extension is |
| /// enabled. |
| RebuildDirtyWidgetCallback? debugOnRebuildDirtyWidget; |
| |
| /// Log all calls to [BuildOwner.buildScope]. |
| /// |
| /// Combined with [debugPrintScheduleBuildForStacks], this allows you to track |
| /// when a [State.setState] call gets serviced. |
| /// |
| /// Combined with [debugPrintRebuildDirtyWidgets] or |
| /// [debugPrintBeginFrameBanner], this allows you to distinguish builds |
| /// triggered by the initial mounting of a widget tree (e.g. in a call to |
| /// [runApp]) from the regular builds triggered by the pipeline. |
| /// |
| /// See also: |
| /// |
| /// * [WidgetsBinding.drawFrame], which pumps the build and rendering pipeline |
| /// to generate a frame. |
| bool debugPrintBuildScope = false; |
| |
| /// Log the call stacks that mark widgets as needing to be rebuilt. |
| /// |
| /// This is called whenever [BuildOwner.scheduleBuildFor] adds an element to the |
| /// dirty list. Typically this is as a result of [Element.markNeedsBuild] being |
| /// called, which itself is usually a result of [State.setState] being called. |
| /// |
| /// To see when a widget is rebuilt, see [debugPrintRebuildDirtyWidgets]. |
| /// |
| /// To see when the dirty list is flushed, see [debugPrintBuildScope]. |
| /// |
| /// To see when a frame is scheduled, see [debugPrintScheduleFrameStacks]. |
| bool debugPrintScheduleBuildForStacks = false; |
| |
| /// Log when widgets with global keys are deactivated and log when they are |
| /// reactivated (retaken). |
| /// |
| /// This can help track down framework bugs relating to the [GlobalKey] logic. |
| bool debugPrintGlobalKeyedWidgetLifecycle = false; |
| |
| /// Adds [Timeline] events for every Widget built. |
| /// |
| /// The timing information this flag exposes is not representative of the actual |
| /// cost of building, because the overhead of adding timeline events is |
| /// significant relative to the time each object takes to build. However, it can |
| /// expose unexpected widget behavior in the timeline. |
| /// |
| /// In debug builds, additional information is included in the trace (such as |
| /// the properties of widgets being built). Collecting this data is |
| /// expensive and further makes these traces non-representative of actual |
| /// performance. This data is omitted in profile builds. |
| /// |
| /// For more information about performance debugging in Flutter, see |
| /// <https://flutter.dev/docs/perf/rendering>. |
| /// |
| /// See also: |
| /// |
| /// * [debugPrintRebuildDirtyWidgets], which does something similar but |
| /// reporting the builds to the console. |
| /// * [debugProfileLayoutsEnabled], which does something similar for layout, |
| /// and [debugPrintLayouts], its console equivalent. |
| /// * [debugProfilePaintsEnabled], which does something similar for painting. |
| /// * [debugProfileBuildsEnabledUserWidgets], which adds events for user-created |
| /// [Widget] build times and incurs less overhead. |
| /// * [debugEnhanceBuildTimelineArguments], which enhances the trace with |
| /// debugging information related to [Widget] builds. |
| bool debugProfileBuildsEnabled = false; |
| |
| /// Adds [Timeline] events for every user-created [Widget] built. |
| /// |
| /// A user-created [Widget] is any [Widget] that is constructed in the root |
| /// library. Often [Widget]s contain child [Widget]s that are constructed in |
| /// libraries (for example, a [TextButton] having a [RichText] child). Timeline |
| /// events for those children will be omitted with this flag. This works for any |
| /// [Widget] not just ones declared in the root library. |
| /// |
| /// See also: |
| /// |
| /// * [debugProfileBuildsEnabled], which functions similarly but shows events |
| /// for every widget and has a higher overhead cost. |
| /// * [debugEnhanceBuildTimelineArguments], which enhances the trace with |
| /// debugging information related to [Widget] builds. |
| bool debugProfileBuildsEnabledUserWidgets = false; |
| |
| /// Adds debugging information to [Timeline] events related to [Widget] builds. |
| /// |
| /// This flag will only add [Timeline] event arguments for debug builds. |
| /// Additional arguments will be added for the "BUILD" [Timeline] event and for |
| /// all [Widget] build [Timeline] events, which are the [Timeline] events that |
| /// are added when either of [debugProfileBuildsEnabled] and |
| /// [debugProfileBuildsEnabledUserWidgets] are true. The debugging information |
| /// that will be added in trace arguments includes stats around [Widget] dirty |
| /// states and [Widget] diagnostic information (i.e. [Widget] properties). |
| /// |
| /// See also: |
| /// |
| /// * [debugProfileBuildsEnabled], which adds [Timeline] events for every |
| /// [Widget] built. |
| /// * [debugProfileBuildsEnabledUserWidgets], which adds [Timeline] events for |
| /// every user-created [Widget] built. |
| /// * [debugEnhanceLayoutTimelineArguments], which does something similar for |
| /// events related to [RenderObject] layouts. |
| /// * [debugEnhancePaintTimelineArguments], which does something similar for |
| /// events related to [RenderObject] paints. |
| bool debugEnhanceBuildTimelineArguments = false; |
| |
| /// Show banners for deprecated widgets. |
| bool debugHighlightDeprecatedWidgets = false; |
| |
| Key? _firstNonUniqueKey(Iterable<Widget> widgets) { |
| final Set<Key> keySet = HashSet<Key>(); |
| for (final Widget widget in widgets) { |
| assert(widget != null); |
| if (widget.key == null) { |
| continue; |
| } |
| if (!keySet.add(widget.key!)) { |
| return widget.key; |
| } |
| } |
| return null; |
| } |
| |
| /// Asserts if the given child list contains any duplicate non-null keys. |
| /// |
| /// To invoke this function, use the following pattern: |
| /// |
| /// ```dart |
| /// class MyWidget extends StatelessWidget { |
| /// MyWidget({ super.key, required this.children }) { |
| /// assert(!debugChildrenHaveDuplicateKeys(this, children)); |
| /// } |
| /// |
| /// final List<Widget> children; |
| /// |
| /// // ... |
| /// } |
| /// ``` |
| /// |
| /// If specified, the `message` overrides the default message. |
| /// |
| /// For a version of this function that can be used in contexts where |
| /// the list of items does not have a particular parent, see |
| /// [debugItemsHaveDuplicateKeys]. |
| /// |
| /// Does nothing if asserts are disabled. Always returns false. |
| bool debugChildrenHaveDuplicateKeys(Widget parent, Iterable<Widget> children, { String? message }) { |
| assert(() { |
| final Key? nonUniqueKey = _firstNonUniqueKey(children); |
| if (nonUniqueKey != null) { |
| throw FlutterError( |
| "${message ?? 'Duplicate keys found.\n' |
| 'If multiple keyed widgets exist as children of another widget, they must have unique keys.'}" |
| '\n$parent has multiple children with key $nonUniqueKey.', |
| ); |
| } |
| return true; |
| }()); |
| return false; |
| } |
| |
| /// Asserts if the given list of items contains any duplicate non-null keys. |
| /// |
| /// To invoke this function, use the following pattern: |
| /// |
| /// ```dart |
| /// assert(!debugItemsHaveDuplicateKeys(items)); |
| /// ``` |
| /// |
| /// For a version of this function specifically intended for parents |
| /// checking their children lists, see [debugChildrenHaveDuplicateKeys]. |
| /// |
| /// Does nothing if asserts are disabled. Always returns false. |
| bool debugItemsHaveDuplicateKeys(Iterable<Widget> items) { |
| assert(() { |
| final Key? nonUniqueKey = _firstNonUniqueKey(items); |
| if (nonUniqueKey != null) { |
| throw FlutterError('Duplicate key found: $nonUniqueKey.'); |
| } |
| return true; |
| }()); |
| return false; |
| } |
| |
| /// Asserts that the given context has a [Table] ancestor. |
| /// |
| /// Used by [TableRowInkWell] to make sure that it is only used in an appropriate context. |
| /// |
| /// To invoke this function, use the following pattern, typically in the |
| /// relevant Widget's build method: |
| /// |
| /// ```dart |
| /// assert(debugCheckHasTable(context)); |
| /// ``` |
| /// |
| /// Always place this before any early returns, so that the invariant is checked |
| /// in all cases. This prevents bugs from hiding until a particular codepath is |
| /// hit. |
| /// |
| /// This method can be expensive (it walks the element tree). |
| /// |
| /// Does nothing if asserts are disabled. Always returns true. |
| bool debugCheckHasTable(BuildContext context) { |
| assert(() { |
| if (context.widget is! Table && context.findAncestorWidgetOfExactType<Table>() == null) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('No Table widget found.'), |
| ErrorDescription('${context.widget.runtimeType} widgets require a Table widget ancestor.'), |
| context.describeWidget('The specific widget that could not find a Table ancestor was'), |
| context.describeOwnershipChain('The ownership chain for the affected widget is'), |
| ]); |
| } |
| return true; |
| }()); |
| return true; |
| } |
| |
| /// Asserts that the given context has a [MediaQuery] ancestor. |
| /// |
| /// Used by various widgets to make sure that they are only used in an |
| /// appropriate context. |
| /// |
| /// To invoke this function, use the following pattern, typically in the |
| /// relevant Widget's build method: |
| /// |
| /// ```dart |
| /// assert(debugCheckHasMediaQuery(context)); |
| /// ``` |
| /// |
| /// Always place this before any early returns, so that the invariant is checked |
| /// in all cases. This prevents bugs from hiding until a particular codepath is |
| /// hit. |
| /// |
| /// Does nothing if asserts are disabled. Always returns true. |
| bool debugCheckHasMediaQuery(BuildContext context) { |
| assert(() { |
| if (context.widget is! MediaQuery && context.getElementForInheritedWidgetOfExactType<MediaQuery>() == null) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('No MediaQuery widget ancestor found.'), |
| ErrorDescription('${context.widget.runtimeType} widgets require a MediaQuery widget ancestor.'), |
| context.describeWidget('The specific widget that could not find a MediaQuery ancestor was'), |
| context.describeOwnershipChain('The ownership chain for the affected widget is'), |
| ErrorHint( |
| 'No MediaQuery ancestor could be found starting from the context ' |
| 'that was passed to MediaQuery.of(). This can happen because you ' |
| 'have not added a WidgetsApp, CupertinoApp, or MaterialApp widget ' |
| '(those widgets introduce a MediaQuery), or it can happen if the ' |
| 'context you use comes from a widget above those widgets.', |
| ), |
| ]); |
| } |
| return true; |
| }()); |
| return true; |
| } |
| |
| /// Asserts that the given context has a [Directionality] ancestor. |
| /// |
| /// Used by various widgets to make sure that they are only used in an |
| /// appropriate context. |
| /// |
| /// To invoke this function, use the following pattern, typically in the |
| /// relevant Widget's build method: |
| /// |
| /// ```dart |
| /// assert(debugCheckHasDirectionality(context)); |
| /// ``` |
| /// |
| /// To improve the error messages you can add some extra color using the |
| /// named arguments. |
| /// |
| /// * why: explain why the direction is needed, for example "to resolve |
| /// the 'alignment' argument". Should be an adverb phrase describing why. |
| /// * hint: explain why this might be happening, for example "The default |
| /// value of the 'alignment' argument of the $runtimeType widget is an |
| /// AlignmentDirectional value.". Should be a fully punctuated sentence. |
| /// * alternative: provide additional advice specific to the situation, |
| /// especially an alternative to providing a Directionality ancestor. |
| /// For example, "Alternatively, consider specifying the 'textDirection' |
| /// argument.". Should be a fully punctuated sentence. |
| /// |
| /// Each one can be null, in which case it is skipped (this is the default). |
| /// If they are non-null, they are included in the order above, interspersed |
| /// with the more generic advice regarding [Directionality]. |
| /// |
| /// Always place this before any early returns, so that the invariant is checked |
| /// in all cases. This prevents bugs from hiding until a particular codepath is |
| /// hit. |
| /// |
| /// Does nothing if asserts are disabled. Always returns true. |
| bool debugCheckHasDirectionality(BuildContext context, { String? why, String? hint, String? alternative }) { |
| assert(() { |
| if (context.widget is! Directionality && context.getElementForInheritedWidgetOfExactType<Directionality>() == null) { |
| why = why == null ? '' : ' $why'; |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('No Directionality widget found.'), |
| ErrorDescription('${context.widget.runtimeType} widgets require a Directionality widget ancestor$why.\n'), |
| if (hint != null) |
| ErrorHint(hint), |
| context.describeWidget('The specific widget that could not find a Directionality ancestor was'), |
| context.describeOwnershipChain('The ownership chain for the affected widget is'), |
| ErrorHint( |
| 'Typically, the Directionality widget is introduced by the MaterialApp ' |
| 'or WidgetsApp widget at the top of your application widget tree. It ' |
| 'determines the ambient reading direction and is used, for example, to ' |
| 'determine how to lay out text, how to interpret "start" and "end" ' |
| 'values, and to resolve EdgeInsetsDirectional, ' |
| 'AlignmentDirectional, and other *Directional objects.', |
| ), |
| if (alternative != null) |
| ErrorHint(alternative), |
| ]); |
| } |
| return true; |
| }()); |
| return true; |
| } |
| |
| /// Asserts that the `built` widget is not null. |
| /// |
| /// Used when the given `widget` calls a builder function to check that the |
| /// function returned a non-null value, as typically required. |
| /// |
| /// Does nothing when asserts are disabled. |
| void debugWidgetBuilderValue(Widget widget, Widget? built) { |
| assert(() { |
| if (built == null) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('A build function returned null.'), |
| DiagnosticsProperty<Widget>('The offending widget is', widget, style: DiagnosticsTreeStyle.errorProperty), |
| ErrorDescription('Build functions must never return null.'), |
| ErrorHint( |
| 'To return an empty space that causes the building widget to fill available room, return "Container()". ' |
| 'To return an empty space that takes as little room as possible, return "Container(width: 0.0, height: 0.0)".', |
| ), |
| ]); |
| } |
| if (widget == built) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('A build function returned context.widget.'), |
| DiagnosticsProperty<Widget>('The offending widget is', widget, style: DiagnosticsTreeStyle.errorProperty), |
| ErrorDescription( |
| 'Build functions must never return their BuildContext parameter\'s widget or a child that contains "context.widget". ' |
| 'Doing so introduces a loop in the widget tree that can cause the app to crash.', |
| ), |
| ]); |
| } |
| return true; |
| }()); |
| } |
| |
| /// Asserts that the given context has a [Localizations] ancestor that contains |
| /// a [WidgetsLocalizations] delegate. |
| /// |
| /// To call this function, use the following pattern, typically in the |
| /// relevant Widget's build method: |
| /// |
| /// ```dart |
| /// assert(debugCheckHasWidgetsLocalizations(context)); |
| /// ``` |
| /// |
| /// Always place this before any early returns, so that the invariant is checked |
| /// in all cases. This prevents bugs from hiding until a particular codepath is |
| /// hit. |
| /// |
| /// Does nothing if asserts are disabled. Always returns true. |
| bool debugCheckHasWidgetsLocalizations(BuildContext context) { |
| assert(() { |
| if (Localizations.of<WidgetsLocalizations>(context, WidgetsLocalizations) == null) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('No WidgetsLocalizations found.'), |
| ErrorDescription( |
| '${context.widget.runtimeType} widgets require WidgetsLocalizations ' |
| 'to be provided by a Localizations widget ancestor.', |
| ), |
| ErrorDescription( |
| 'The widgets library uses Localizations to generate messages, ' |
| 'labels, and abbreviations.', |
| ), |
| ErrorHint( |
| 'To introduce a WidgetsLocalizations, either use a ' |
| 'WidgetsApp at the root of your application to include them ' |
| 'automatically, or add a Localization widget with a ' |
| 'WidgetsLocalizations delegate.', |
| ), |
| ...context.describeMissingAncestor(expectedAncestorType: WidgetsLocalizations), |
| ]); |
| } |
| return true; |
| }()); |
| return true; |
| } |
| |
| /// Asserts that the given context has an [Overlay] ancestor. |
| /// |
| /// To call this function, use the following pattern, typically in the |
| /// relevant Widget's build method: |
| /// |
| /// ```dart |
| /// assert(debugCheckHasOverlay(context)); |
| /// ``` |
| /// |
| /// Always place this before any early returns, so that the invariant is checked |
| /// in all cases. This prevents bugs from hiding until a particular codepath is |
| /// hit. |
| /// |
| /// This method can be expensive (it walks the element tree). |
| /// |
| /// Does nothing if asserts are disabled. Always returns true. |
| bool debugCheckHasOverlay(BuildContext context) { |
| assert(() { |
| if (context.widget is! Overlay && context.findAncestorWidgetOfExactType<Overlay>() == null) { |
| throw FlutterError.fromParts(<DiagnosticsNode>[ |
| ErrorSummary('No Overlay widget found.'), |
| ErrorDescription( |
| '${context.widget.runtimeType} widgets require an Overlay ' |
| 'widget ancestor.\n' |
| 'An overlay lets widgets float on top of other widget children.', |
| ), |
| ErrorHint( |
| 'To introduce an Overlay widget, you can either directly ' |
| 'include one, or use a widget that contains an Overlay itself, ' |
| 'such as a Navigator, WidgetApp, MaterialApp, or CupertinoApp.', |
| ), |
| ...context.describeMissingAncestor(expectedAncestorType: Overlay), |
| ]); |
| } |
| return true; |
| }()); |
| return true; |
| } |
| |
| /// Returns true if none of the widget library debug variables have been changed. |
| /// |
| /// This function is used by the test framework to ensure that debug variables |
| /// haven't been inadvertently changed. |
| /// |
| /// See [the widgets library](widgets/widgets-library.html) for a complete list. |
| bool debugAssertAllWidgetVarsUnset(String reason) { |
| assert(() { |
| if (debugPrintRebuildDirtyWidgets || |
| debugPrintBuildScope || |
| debugPrintScheduleBuildForStacks || |
| debugPrintGlobalKeyedWidgetLifecycle || |
| debugProfileBuildsEnabled || |
| debugHighlightDeprecatedWidgets || |
| debugProfileBuildsEnabledUserWidgets) { |
| throw FlutterError(reason); |
| } |
| return true; |
| }()); |
| return true; |
| } |