| // 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 'lookup_boundary.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 | |
| /// Flutter DevTools 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) { | |
| 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 (LookupBoundary.findAncestorWidgetOfExactType<Overlay>(context) == null) { | |
| final bool hiddenByBoundary = LookupBoundary.debugIsHidingAncestorWidgetOfExactType<Overlay>(context); | |
| throw FlutterError.fromParts(<DiagnosticsNode>[ | |
| ErrorSummary('No Overlay widget found${hiddenByBoundary ? ' within the closest LookupBoundary' : ''}.'), | |
| if (hiddenByBoundary) | |
| ErrorDescription( | |
| 'There is an ancestor Overlay widget, but it is hidden by a LookupBoundary.' | |
| ), | |
| ErrorDescription( | |
| '${context.widget.runtimeType} widgets require an Overlay ' | |
| 'widget ancestor within the closest LookupBoundary.\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; | |
| } |