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