// 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 'media_query.dart'; import 'table.dart'; // 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. /// /// For details on how to use [Timeline] events in the Dart Observatory to /// optimize your app, see https://flutter.dev/docs/testing/debugging#tracing-any-dart-code-performance /// and https://fuchsia.googlesource.com/topaz/+/master/shell/docs/performance.md /// /// See also: /// /// * [debugProfilePaintsEnabled], which does something similar but for /// painting, and [debugPrintRebuildDirtyWidgets], which does something similar /// but reporting the builds to the console. bool debugProfileBuildsEnabled = false; /// Show banners for deprecated widgets. bool debugHighlightDeprecatedWidgets = false; Key _firstNonUniqueKey(Iterable<Widget> widgets) { final Set<Key> keySet = HashSet<Key>(); for (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, typically in the /// relevant Widget's constructor: /// /// ```dart /// assert(!debugChildrenHaveDuplicateKeys(this, children)); /// ``` /// /// 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 true. bool debugChildrenHaveDuplicateKeys(Widget parent, Iterable<Widget> children) { assert(() { final Key nonUniqueKey = _firstNonUniqueKey(children); if (nonUniqueKey != null) { throw FlutterError.fromParts(<DiagnosticsNode>[ ErrorSummary('Duplicate keys found.'), ErrorDescription( 'If multiple keyed nodes exist as children of another node, 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 true. bool debugItemsHaveDuplicateKeys(Iterable<Widget> items) { assert(() { final Key nonUniqueKey = _firstNonUniqueKey(items); if (nonUniqueKey != null) throw FlutterError.fromParts(<DiagnosticsNode>[ ErrorSummary('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)); /// ``` /// /// 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)); /// ``` /// /// Does nothing if asserts are disabled. Always returns true. bool debugCheckHasMediaQuery(BuildContext context) { assert(() { if (context.widget is! MediaQuery && context.findAncestorWidgetOfExactType<MediaQuery>() == null) { throw FlutterError.fromParts(<DiagnosticsNode>[ ErrorSummary('No MediaQuery widget 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( 'Typically, the MediaQuery widget is introduced by the MaterialApp or ' 'WidgetsApp widget at the top of your application widget tree.' ), ]); } 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)); /// ``` /// /// Does nothing if asserts are disabled. Always returns true. bool debugCheckHasDirectionality(BuildContext context) { assert(() { if (context.widget is! Directionality && context.findAncestorWidgetOfExactType<Directionality>() == null) { throw FlutterError.fromParts(<DiagnosticsNode>[ ErrorSummary('No Directionality widget found.'), ErrorDescription('${context.widget.runtimeType} widgets require a Directionality widget ancestor.\n'), 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.' ), ]); } 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; }()); } /// 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) { throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('$reason')]); } return true; }()); return true; }