mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
283 lines
10 KiB
Dart
283 lines
10 KiB
Dart
// Copyright 2015 The Chromium 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 '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 the discussion at [WidgetsBinding.drawFrame].
|
|
bool debugPrintRebuildDirtyWidgets = false;
|
|
|
|
/// 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 the discussion at [WidgetsBinding.drawFrame].
|
|
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://fuchsia.googlesource.com/sysui/+/master/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 = new 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 new FlutterError(
|
|
'Duplicate keys found.\n'
|
|
'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 new 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));
|
|
/// ```
|
|
///
|
|
/// Does nothing if asserts are disabled. Always returns true.
|
|
bool debugCheckHasTable(BuildContext context) {
|
|
assert(() {
|
|
if (context.widget is! Table && context.ancestorWidgetOfExactType(Table) == null) {
|
|
final Element element = context;
|
|
throw new FlutterError(
|
|
'No Table widget found.\n'
|
|
'${context.widget.runtimeType} widgets require a Table widget ancestor.\n'
|
|
'The specific widget that could not find a Table ancestor was:\n'
|
|
' ${context.widget}\n'
|
|
'The ownership chain for the affected widget is:\n'
|
|
' ${element.debugGetCreatorChain(10)}'
|
|
);
|
|
}
|
|
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.ancestorWidgetOfExactType(MediaQuery) == null) {
|
|
final Element element = context;
|
|
throw new FlutterError(
|
|
'No MediaQuery widget found.\n'
|
|
'${context.widget.runtimeType} widgets require a MediaQuery widget ancestor.\n'
|
|
'The specific widget that could not find a MediaQuery ancestor was:\n'
|
|
' ${context.widget}\n'
|
|
'The ownership chain for the affected widget is:\n'
|
|
' ${element.debugGetCreatorChain(10)}\n'
|
|
'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.ancestorWidgetOfExactType(Directionality) == null) {
|
|
final Element element = context;
|
|
throw new FlutterError(
|
|
'No Directionality widget found.\n'
|
|
'${context.widget.runtimeType} widgets require a Directionality widget ancestor.\n'
|
|
'The specific widget that could not find a Directionality ancestor was:\n'
|
|
' ${context.widget}\n'
|
|
'The ownership chain for the affected widget is:\n'
|
|
' ${element.debugGetCreatorChain(10)}\n'
|
|
'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 new FlutterError(
|
|
'A build function returned null.\n'
|
|
'The offending widget is: $widget\n'
|
|
'Build functions must never return null. '
|
|
'To return an empty space that causes the building widget to fill available room, return "new Container()". '
|
|
'To return an empty space that takes as little room as possible, return "new Container(width: 0.0, height: 0.0)".'
|
|
);
|
|
}
|
|
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 [https://docs.flutter.io/flutter/widgets/widgets-library.html] for
|
|
/// a complete list.
|
|
bool debugAssertAllWidgetVarsUnset(String reason) {
|
|
assert(() {
|
|
if (debugPrintRebuildDirtyWidgets ||
|
|
debugPrintBuildScope ||
|
|
debugPrintScheduleBuildForStacks ||
|
|
debugPrintGlobalKeyedWidgetLifecycle ||
|
|
debugProfileBuildsEnabled ||
|
|
debugHighlightDeprecatedWidgets) {
|
|
throw new FlutterError(reason);
|
|
}
|
|
return true;
|
|
}());
|
|
return true;
|
|
}
|