1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:collection';
6import 'dart:developer' show Timeline; // to disambiguate reference in dartdocs below
7
8import 'package:flutter/foundation.dart';
9
10import 'basic.dart';
11import 'framework.dart';
12import 'localizations.dart';
13import 'lookup_boundary.dart';
14import 'media_query.dart';
15import 'overlay.dart';
16import 'table.dart';
17
18// Examples can assume:
19// late BuildContext context;
20// List children = [];
21// List items = [];
22
23// Any changes to this file should be reflected in the debugAssertAllWidgetVarsUnset()
24// function below.
25
26/// Log the dirty widgets that are built each frame.
27///
28/// Combined with [debugPrintBuildScope] or [debugPrintBeginFrameBanner], this
29/// allows you to distinguish builds triggered by the initial mounting of a
30/// widget tree (e.g. in a call to [runApp]) from the regular builds triggered
31/// by the pipeline.
32///
33/// Combined with [debugPrintScheduleBuildForStacks], this lets you watch a
34/// widget's dirty/clean lifecycle.
35///
36/// To get similar information but showing it on the timeline available from
37/// Flutter DevTools rather than getting it in the console (where it can be
38/// overwhelming), consider [debugProfileBuildsEnabled].
39///
40/// See also:
41///
42/// * [WidgetsBinding.drawFrame], which pumps the build and rendering pipeline
43/// to generate a frame.
44bool debugPrintRebuildDirtyWidgets = false;
45
46/// Signature for [debugOnRebuildDirtyWidget] implementations.
47typedef RebuildDirtyWidgetCallback = void Function(Element e, bool builtOnce);
48
49/// Callback invoked for every dirty widget built each frame.
50///
51/// This callback is only invoked in debug builds.
52///
53/// See also:
54///
55/// * [debugPrintRebuildDirtyWidgets], which does something similar but logs
56/// to the console instead of invoking a callback.
57/// * [debugOnProfilePaint], which does something similar for [RenderObject]
58/// painting.
59/// * [WidgetInspectorService], which uses the [debugOnRebuildDirtyWidget]
60/// callback to generate aggregate profile statistics describing which widget
61/// rebuilds occurred when the
62/// `ext.flutter.inspector.trackRebuildDirtyWidgets` service extension is
63/// enabled.
64RebuildDirtyWidgetCallback? debugOnRebuildDirtyWidget;
65
66/// Log all calls to [BuildOwner.buildScope].
67///
68/// Combined with [debugPrintScheduleBuildForStacks], this allows you to track
69/// when a [State.setState] call gets serviced.
70///
71/// Combined with [debugPrintRebuildDirtyWidgets] or
72/// [debugPrintBeginFrameBanner], this allows you to distinguish builds
73/// triggered by the initial mounting of a widget tree (e.g. in a call to
74/// [runApp]) from the regular builds triggered by the pipeline.
75///
76/// See also:
77///
78/// * [WidgetsBinding.drawFrame], which pumps the build and rendering pipeline
79/// to generate a frame.
80bool debugPrintBuildScope = false;
81
82/// Log the call stacks that mark widgets as needing to be rebuilt.
83///
84/// This is called whenever [BuildOwner.scheduleBuildFor] adds an element to the
85/// dirty list. Typically this is as a result of [Element.markNeedsBuild] being
86/// called, which itself is usually a result of [State.setState] being called.
87///
88/// To see when a widget is rebuilt, see [debugPrintRebuildDirtyWidgets].
89///
90/// To see when the dirty list is flushed, see [debugPrintBuildScope].
91///
92/// To see when a frame is scheduled, see [debugPrintScheduleFrameStacks].
93bool debugPrintScheduleBuildForStacks = false;
94
95/// Log when widgets with global keys are deactivated and log when they are
96/// reactivated (retaken).
97///
98/// This can help track down framework bugs relating to the [GlobalKey] logic.
99bool debugPrintGlobalKeyedWidgetLifecycle = false;
100
101/// Adds [Timeline] events for every Widget built.
102///
103/// The timing information this flag exposes is not representative of the actual
104/// cost of building, because the overhead of adding timeline events is
105/// significant relative to the time each object takes to build. However, it can
106/// expose unexpected widget behavior in the timeline.
107///
108/// In debug builds, additional information is included in the trace (such as
109/// the properties of widgets being built). Collecting this data is
110/// expensive and further makes these traces non-representative of actual
111/// performance. This data is omitted in profile builds.
112///
113/// For more information about performance debugging in Flutter, see
114/// <https://flutter.dev/docs/perf/rendering>.
115///
116/// See also:
117///
118/// * [debugPrintRebuildDirtyWidgets], which does something similar but
119/// reporting the builds to the console.
120/// * [debugProfileLayoutsEnabled], which does something similar for layout,
121/// and [debugPrintLayouts], its console equivalent.
122/// * [debugProfilePaintsEnabled], which does something similar for painting.
123/// * [debugProfileBuildsEnabledUserWidgets], which adds events for user-created
124/// [Widget] build times and incurs less overhead.
125/// * [debugEnhanceBuildTimelineArguments], which enhances the trace with
126/// debugging information related to [Widget] builds.
127bool debugProfileBuildsEnabled = false;
128
129/// Adds [Timeline] events for every user-created [Widget] built.
130///
131/// A user-created [Widget] is any [Widget] that is constructed in the root
132/// library. Often [Widget]s contain child [Widget]s that are constructed in
133/// libraries (for example, a [TextButton] having a [RichText] child). Timeline
134/// events for those children will be omitted with this flag. This works for any
135/// [Widget] not just ones declared in the root library.
136///
137/// See also:
138///
139/// * [debugProfileBuildsEnabled], which functions similarly but shows events
140/// for every widget and has a higher overhead cost.
141/// * [debugEnhanceBuildTimelineArguments], which enhances the trace with
142/// debugging information related to [Widget] builds.
143bool debugProfileBuildsEnabledUserWidgets = false;
144
145/// Adds debugging information to [Timeline] events related to [Widget] builds.
146///
147/// This flag will only add [Timeline] event arguments for debug builds.
148/// Additional arguments will be added for the "BUILD" [Timeline] event and for
149/// all [Widget] build [Timeline] events, which are the [Timeline] events that
150/// are added when either of [debugProfileBuildsEnabled] and
151/// [debugProfileBuildsEnabledUserWidgets] are true. The debugging information
152/// that will be added in trace arguments includes stats around [Widget] dirty
153/// states and [Widget] diagnostic information (i.e. [Widget] properties).
154///
155/// See also:
156///
157/// * [debugProfileBuildsEnabled], which adds [Timeline] events for every
158/// [Widget] built.
159/// * [debugProfileBuildsEnabledUserWidgets], which adds [Timeline] events for
160/// every user-created [Widget] built.
161/// * [debugEnhanceLayoutTimelineArguments], which does something similar for
162/// events related to [RenderObject] layouts.
163/// * [debugEnhancePaintTimelineArguments], which does something similar for
164/// events related to [RenderObject] paints.
165bool debugEnhanceBuildTimelineArguments = false;
166
167/// Show banners for deprecated widgets.
168bool debugHighlightDeprecatedWidgets = false;
169
170Key? _firstNonUniqueKey(Iterable<Widget> widgets) {
171 final Set<Key> keySet = HashSet<Key>();
172 for (final Widget widget in widgets) {
173 if (widget.key == null) {
174 continue;
175 }
176 if (!keySet.add(widget.key!)) {
177 return widget.key;
178 }
179 }
180 return null;
181}
182
183/// Asserts if the given child list contains any duplicate non-null keys.
184///
185/// To invoke this function, use the following pattern:
186///
187/// ```dart
188/// class MyWidget extends StatelessWidget {
189/// MyWidget({ super.key, required this.children }) {
190/// assert(!debugChildrenHaveDuplicateKeys(this, children));
191/// }
192///
193/// final List<Widget> children;
194///
195/// // ...
196/// }
197/// ```
198///
199/// If specified, the `message` overrides the default message.
200///
201/// For a version of this function that can be used in contexts where
202/// the list of items does not have a particular parent, see
203/// [debugItemsHaveDuplicateKeys].
204///
205/// Does nothing if asserts are disabled. Always returns false.
206bool debugChildrenHaveDuplicateKeys(Widget parent, Iterable<Widget> children, { String? message }) {
207 assert(() {
208 final Key? nonUniqueKey = _firstNonUniqueKey(children);
209 if (nonUniqueKey != null) {
210 throw FlutterError(
211 "${message ?? 'Duplicate keys found.\n'
212 'If multiple keyed widgets exist as children of another widget, they must have unique keys.'}"
213 '\n$parent has multiple children with key $nonUniqueKey.',
214 );
215 }
216 return true;
217 }());
218 return false;
219}
220
221/// Asserts if the given list of items contains any duplicate non-null keys.
222///
223/// To invoke this function, use the following pattern:
224///
225/// ```dart
226/// assert(!debugItemsHaveDuplicateKeys(items));
227/// ```
228///
229/// For a version of this function specifically intended for parents
230/// checking their children lists, see [debugChildrenHaveDuplicateKeys].
231///
232/// Does nothing if asserts are disabled. Always returns false.
233bool debugItemsHaveDuplicateKeys(Iterable<Widget> items) {
234 assert(() {
235 final Key? nonUniqueKey = _firstNonUniqueKey(items);
236 if (nonUniqueKey != null) {
237 throw FlutterError('Duplicate key found: $nonUniqueKey.');
238 }
239 return true;
240 }());
241 return false;
242}
243
244/// Asserts that the given context has a [Table] ancestor.
245///
246/// Used by [TableRowInkWell] to make sure that it is only used in an appropriate context.
247///
248/// To invoke this function, use the following pattern, typically in the
249/// relevant Widget's build method:
250///
251/// ```dart
252/// assert(debugCheckHasTable(context));
253/// ```
254///
255/// Always place this before any early returns, so that the invariant is checked
256/// in all cases. This prevents bugs from hiding until a particular codepath is
257/// hit.
258///
259/// This method can be expensive (it walks the element tree).
260///
261/// Does nothing if asserts are disabled. Always returns true.
262bool debugCheckHasTable(BuildContext context) {
263 assert(() {
264 if (context.widget is! Table && context.findAncestorWidgetOfExactType<Table>() == null) {
265 throw FlutterError.fromParts(<DiagnosticsNode>[
266 ErrorSummary('No Table widget found.'),
267 ErrorDescription('${context.widget.runtimeType} widgets require a Table widget ancestor.'),
268 context.describeWidget('The specific widget that could not find a Table ancestor was'),
269 context.describeOwnershipChain('The ownership chain for the affected widget is'),
270 ]);
271 }
272 return true;
273 }());
274 return true;
275}
276
277/// Asserts that the given context has a [MediaQuery] ancestor.
278///
279/// Used by various widgets to make sure that they are only used in an
280/// appropriate context.
281///
282/// To invoke this function, use the following pattern, typically in the
283/// relevant Widget's build method:
284///
285/// ```dart
286/// assert(debugCheckHasMediaQuery(context));
287/// ```
288///
289/// Always place this before any early returns, so that the invariant is checked
290/// in all cases. This prevents bugs from hiding until a particular codepath is
291/// hit.
292///
293/// Does nothing if asserts are disabled. Always returns true.
294bool debugCheckHasMediaQuery(BuildContext context) {
295 assert(() {
296 if (context.widget is! MediaQuery && context.getElementForInheritedWidgetOfExactType<MediaQuery>() == null) {
297 throw FlutterError.fromParts(<DiagnosticsNode>[
298 ErrorSummary('No MediaQuery widget ancestor found.'),
299 ErrorDescription('${context.widget.runtimeType} widgets require a MediaQuery widget ancestor.'),
300 context.describeWidget('The specific widget that could not find a MediaQuery ancestor was'),
301 context.describeOwnershipChain('The ownership chain for the affected widget is'),
302 ErrorHint(
303 'No MediaQuery ancestor could be found starting from the context '
304 'that was passed to MediaQuery.of(). This can happen because the '
305 'context used is not a descendant of a View widget, which introduces '
306 'a MediaQuery.'
307 ),
308 ]);
309 }
310 return true;
311 }());
312 return true;
313}
314
315/// Asserts that the given context has a [Directionality] ancestor.
316///
317/// Used by various widgets to make sure that they are only used in an
318/// appropriate context.
319///
320/// To invoke this function, use the following pattern, typically in the
321/// relevant Widget's build method:
322///
323/// ```dart
324/// assert(debugCheckHasDirectionality(context));
325/// ```
326///
327/// To improve the error messages you can add some extra color using the
328/// named arguments.
329///
330/// * why: explain why the direction is needed, for example "to resolve
331/// the 'alignment' argument". Should be an adverb phrase describing why.
332/// * hint: explain why this might be happening, for example "The default
333/// value of the 'alignment' argument of the $runtimeType widget is an
334/// AlignmentDirectional value.". Should be a fully punctuated sentence.
335/// * alternative: provide additional advice specific to the situation,
336/// especially an alternative to providing a Directionality ancestor.
337/// For example, "Alternatively, consider specifying the 'textDirection'
338/// argument.". Should be a fully punctuated sentence.
339///
340/// Each one can be null, in which case it is skipped (this is the default).
341/// If they are non-null, they are included in the order above, interspersed
342/// with the more generic advice regarding [Directionality].
343///
344/// Always place this before any early returns, so that the invariant is checked
345/// in all cases. This prevents bugs from hiding until a particular codepath is
346/// hit.
347///
348/// Does nothing if asserts are disabled. Always returns true.
349bool debugCheckHasDirectionality(BuildContext context, { String? why, String? hint, String? alternative }) {
350 assert(() {
351 if (context.widget is! Directionality && context.getElementForInheritedWidgetOfExactType<Directionality>() == null) {
352 why = why == null ? '' : ' $why';
353 throw FlutterError.fromParts(<DiagnosticsNode>[
354 ErrorSummary('No Directionality widget found.'),
355 ErrorDescription('${context.widget.runtimeType} widgets require a Directionality widget ancestor$why.\n'),
356 if (hint != null)
357 ErrorHint(hint),
358 context.describeWidget('The specific widget that could not find a Directionality ancestor was'),
359 context.describeOwnershipChain('The ownership chain for the affected widget is'),
360 ErrorHint(
361 'Typically, the Directionality widget is introduced by the MaterialApp '
362 'or WidgetsApp widget at the top of your application widget tree. It '
363 'determines the ambient reading direction and is used, for example, to '
364 'determine how to lay out text, how to interpret "start" and "end" '
365 'values, and to resolve EdgeInsetsDirectional, '
366 'AlignmentDirectional, and other *Directional objects.',
367 ),
368 if (alternative != null)
369 ErrorHint(alternative),
370 ]);
371 }
372 return true;
373 }());
374 return true;
375}
376
377/// Asserts that the `built` widget is not null.
378///
379/// Used when the given `widget` calls a builder function to check that the
380/// function returned a non-null value, as typically required.
381///
382/// Does nothing when asserts are disabled.
383void debugWidgetBuilderValue(Widget widget, Widget? built) {
384 assert(() {
385 if (built == null) {
386 throw FlutterError.fromParts(<DiagnosticsNode>[
387 ErrorSummary('A build function returned null.'),
388 DiagnosticsProperty<Widget>('The offending widget is', widget, style: DiagnosticsTreeStyle.errorProperty),
389 ErrorDescription('Build functions must never return null.'),
390 ErrorHint(
391 'To return an empty space that causes the building widget to fill available room, return "Container()". '
392 'To return an empty space that takes as little room as possible, return "Container(width: 0.0, height: 0.0)".',
393 ),
394 ]);
395 }
396 if (widget == built) {
397 throw FlutterError.fromParts(<DiagnosticsNode>[
398 ErrorSummary('A build function returned context.widget.'),
399 DiagnosticsProperty<Widget>('The offending widget is', widget, style: DiagnosticsTreeStyle.errorProperty),
400 ErrorDescription(
401 'Build functions must never return their BuildContext parameter\'s widget or a child that contains "context.widget". '
402 'Doing so introduces a loop in the widget tree that can cause the app to crash.',
403 ),
404 ]);
405 }
406 return true;
407 }());
408}
409
410/// Asserts that the given context has a [Localizations] ancestor that contains
411/// a [WidgetsLocalizations] delegate.
412///
413/// To call this function, use the following pattern, typically in the
414/// relevant Widget's build method:
415///
416/// ```dart
417/// assert(debugCheckHasWidgetsLocalizations(context));
418/// ```
419///
420/// Always place this before any early returns, so that the invariant is checked
421/// in all cases. This prevents bugs from hiding until a particular codepath is
422/// hit.
423///
424/// Does nothing if asserts are disabled. Always returns true.
425bool debugCheckHasWidgetsLocalizations(BuildContext context) {
426 assert(() {
427 if (Localizations.of<WidgetsLocalizations>(context, WidgetsLocalizations) == null) {
428 throw FlutterError.fromParts(<DiagnosticsNode>[
429 ErrorSummary('No WidgetsLocalizations found.'),
430 ErrorDescription(
431 '${context.widget.runtimeType} widgets require WidgetsLocalizations '
432 'to be provided by a Localizations widget ancestor.',
433 ),
434 ErrorDescription(
435 'The widgets library uses Localizations to generate messages, '
436 'labels, and abbreviations.',
437 ),
438 ErrorHint(
439 'To introduce a WidgetsLocalizations, either use a '
440 'WidgetsApp at the root of your application to include them '
441 'automatically, or add a Localization widget with a '
442 'WidgetsLocalizations delegate.',
443 ),
444 ...context.describeMissingAncestor(expectedAncestorType: WidgetsLocalizations),
445 ]);
446 }
447 return true;
448 }());
449 return true;
450}
451
452/// Asserts that the given context has an [Overlay] ancestor.
453///
454/// To call this function, use the following pattern, typically in the
455/// relevant Widget's build method:
456///
457/// ```dart
458/// assert(debugCheckHasOverlay(context));
459/// ```
460///
461/// Always place this before any early returns, so that the invariant is checked
462/// in all cases. This prevents bugs from hiding until a particular codepath is
463/// hit.
464///
465/// This method can be expensive (it walks the element tree).
466///
467/// Does nothing if asserts are disabled. Always returns true.
468bool debugCheckHasOverlay(BuildContext context) {
469 assert(() {
470 if (LookupBoundary.findAncestorWidgetOfExactType<Overlay>(context) == null) {
471 final bool hiddenByBoundary = LookupBoundary.debugIsHidingAncestorWidgetOfExactType<Overlay>(context);
472 throw FlutterError.fromParts(<DiagnosticsNode>[
473 ErrorSummary('No Overlay widget found${hiddenByBoundary ? ' within the closest LookupBoundary' : ''}.'),
474 if (hiddenByBoundary)
475 ErrorDescription(
476 'There is an ancestor Overlay widget, but it is hidden by a LookupBoundary.'
477 ),
478 ErrorDescription(
479 '${context.widget.runtimeType} widgets require an Overlay '
480 'widget ancestor within the closest LookupBoundary.\n'
481 'An overlay lets widgets float on top of other widget children.',
482 ),
483 ErrorHint(
484 'To introduce an Overlay widget, you can either directly '
485 'include one, or use a widget that contains an Overlay itself, '
486 'such as a Navigator, WidgetApp, MaterialApp, or CupertinoApp.',
487 ),
488 ...context.describeMissingAncestor(expectedAncestorType: Overlay),
489 ]);
490 }
491 return true;
492 }());
493 return true;
494}
495
496/// Returns true if none of the widget library debug variables have been changed.
497///
498/// This function is used by the test framework to ensure that debug variables
499/// haven't been inadvertently changed.
500///
501/// See [the widgets library](widgets/widgets-library.html) for a complete list.
502bool debugAssertAllWidgetVarsUnset(String reason) {
503 assert(() {
504 if (debugPrintRebuildDirtyWidgets ||
505 debugPrintBuildScope ||
506 debugPrintScheduleBuildForStacks ||
507 debugPrintGlobalKeyedWidgetLifecycle ||
508 debugProfileBuildsEnabled ||
509 debugHighlightDeprecatedWidgets ||
510 debugProfileBuildsEnabledUserWidgets) {
511 throw FlutterError(reason);
512 }
513 return true;
514 }());
515 return true;
516}
517