|
2 | 2 | // Use of this source code is governed by a BSD-style license that can be |
3 | 3 | // found in the LICENSE file. |
4 | 4 |
|
| 5 | +import 'dart:async'; |
5 | 6 | import 'dart:convert' show json; |
6 | 7 | import 'dart:developer' as developer; |
7 | 8 | import 'dart:io' show exit; |
@@ -265,6 +266,7 @@ abstract class BindingBase { |
265 | 266 | assert(_debugInitializedType == null); |
266 | 267 | assert(() { |
267 | 268 | _debugInitializedType = runtimeType; |
| 269 | + _debugBindingZone = Zone.current; |
268 | 270 | return true; |
269 | 271 | }()); |
270 | 272 | } |
@@ -319,7 +321,7 @@ abstract class BindingBase { |
319 | 321 | ), |
320 | 322 | ErrorHint( |
321 | 323 | 'It is also possible that $T does not implement "initInstances()" to assign a value to "instance". See the ' |
322 | | - 'documentation of the BaseBinding class for more details.', |
| 324 | + 'documentation of the BindingBase class for more details.', |
323 | 325 | ), |
324 | 326 | ErrorHint( |
325 | 327 | 'The binding that was initialized was of the type "$_debugInitializedType". ' |
@@ -399,6 +401,95 @@ abstract class BindingBase { |
399 | 401 | return _debugInitializedType; |
400 | 402 | } |
401 | 403 |
|
| 404 | + Zone? _debugBindingZone; |
| 405 | + |
| 406 | + /// Whether [debugCheckZone] should throw (true) or just report the error (false). |
| 407 | + /// |
| 408 | + /// Setting this to true makes it easier to catch cases where the zones are |
| 409 | + /// misconfigured, by allowing debuggers to stop at the point of error. |
| 410 | + /// |
| 411 | + /// Currently this defaults to false, to avoid suddenly breaking applications |
| 412 | + /// that are affected by this check but appear to be working today. Applications |
| 413 | + /// are encouraged to resolve any issues that cause the [debugCheckZone] message |
| 414 | + /// to appear, as even if they appear to be working today, they are likely to be |
| 415 | + /// hiding hard-to-find bugs, and are more brittle (likely to collect bugs in |
| 416 | + /// the future). |
| 417 | + /// |
| 418 | + /// To silence the message displayed by [debugCheckZone], ensure that the same |
| 419 | + /// zone is used when calling `ensureInitialized()` as when calling the framework |
| 420 | + /// in any other context (e.g. via [runApp]). |
| 421 | + static bool debugZoneErrorsAreFatal = false; |
| 422 | + |
| 423 | + /// Checks that the current [Zone] is the same as that which was used |
| 424 | + /// to initialize the binding. |
| 425 | + /// |
| 426 | + /// If the current zone ([Zone.current]) is not the zone that was active when |
| 427 | + /// the binding was initialized, then this method generates a [FlutterError] |
| 428 | + /// exception with detailed information. The exception is either thrown |
| 429 | + /// directly, or reported via [FlutterError.reportError], depending on the |
| 430 | + /// value of [BindingBase.debugZoneErrorsAreFatal]. |
| 431 | + /// |
| 432 | + /// To silence the message displayed by [debugCheckZone], ensure that the same |
| 433 | + /// zone is used when calling `ensureInitialized()` as when calling the |
| 434 | + /// framework in any other context (e.g. via [runApp]). For example, consider |
| 435 | + /// keeping a reference to the zone used to initialize the binding, and using |
| 436 | + /// [Zone.run] to use it again when calling into the framework. |
| 437 | + /// |
| 438 | + /// ## Usage |
| 439 | + /// |
| 440 | + /// The binding is considered initialized once [BindingBase.initInstances] has |
| 441 | + /// run; if this is called before then, it will throw an [AssertionError]. |
| 442 | + /// |
| 443 | + /// The `entryPoint` parameter is the name of the API that is checking the |
| 444 | + /// zones are consistent, for example, `'runApp'`. |
| 445 | + /// |
| 446 | + /// This function always returns true (if it does not throw). It is expected |
| 447 | + /// to be invoked via the binding instance, e.g.: |
| 448 | + /// |
| 449 | + /// ```dart |
| 450 | + /// void startup() { |
| 451 | + /// WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); |
| 452 | + /// assert(binding.debugCheckZone('startup')); |
| 453 | + /// // ... |
| 454 | + /// } |
| 455 | + /// ``` |
| 456 | + /// |
| 457 | + /// If the binding expects to be used with multiple zones, it should override |
| 458 | + /// this method to return true always without throwing. (For example, the |
| 459 | + /// bindings used with [flutter_test] do this as they make heavy use of zones |
| 460 | + /// to drive the framework with an artificial clock and to catch errors and |
| 461 | + /// report them as test failures.) |
| 462 | + bool debugCheckZone(String entryPoint) { |
| 463 | + assert(() { |
| 464 | + assert(_debugBindingZone != null, 'debugCheckZone can only be used after the binding is fully initialized.'); |
| 465 | + if (Zone.current != _debugBindingZone) { |
| 466 | + final Error message = FlutterError( |
| 467 | + 'Zone mismatch.\n' |
| 468 | + 'The Flutter bindings were initialized in a different zone than is now being used. ' |
| 469 | + 'This will likely cause confusion and bugs as any zone-specific configuration will ' |
| 470 | + 'inconsistently use the configuration of the original binding initialization zone ' |
| 471 | + 'or this zone based on hard-to-predict factors such as which zone was active when ' |
| 472 | + 'a particular callback was set.\n' |
| 473 | + 'It is important to use the same zone when calling `ensureInitialized` on the binding ' |
| 474 | + 'as when calling `$entryPoint` later.\n' |
| 475 | + 'To make this ${ debugZoneErrorsAreFatal ? 'error non-fatal' : 'warning fatal' }, ' |
| 476 | + 'set BindingBase.debugZoneErrorsAreFatal to ${!debugZoneErrorsAreFatal} before the ' |
| 477 | + 'bindings are initialized (i.e. as the first statement in `void main() { }`).', |
| 478 | + ); |
| 479 | + if (debugZoneErrorsAreFatal) { |
| 480 | + throw message; |
| 481 | + } |
| 482 | + FlutterError.reportError(FlutterErrorDetails( |
| 483 | + exception: message, |
| 484 | + stack: StackTrace.current, |
| 485 | + context: ErrorDescription('during $entryPoint'), |
| 486 | + )); |
| 487 | + } |
| 488 | + return true; |
| 489 | + }()); |
| 490 | + return true; |
| 491 | + } |
| 492 | + |
402 | 493 | /// Called when the binding is initialized, to register service |
403 | 494 | /// extensions. |
404 | 495 | /// |
|
0 commit comments