From fce2f93b75ffc772f4046b62cf5bccb8f9691bf7 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Fri, 11 Feb 2022 14:35:09 -0800 Subject: [PATCH] Add a BindingBase.debugBindingType() method to enable asserts that want to verify that the binding isn't initialized (#98226) --- .../flutter/lib/src/foundation/binding.dart | 51 +++++++++++++++---- .../flutter/test/foundation/binding_test.dart | 37 ++++++++++++++ 2 files changed, 79 insertions(+), 9 deletions(-) create mode 100644 packages/flutter/test/foundation/binding_test.dart diff --git a/packages/flutter/lib/src/foundation/binding.dart b/packages/flutter/lib/src/foundation/binding.dart index ab99b293a11..b9f4d736790 100644 --- a/packages/flutter/lib/src/foundation/binding.dart +++ b/packages/flutter/lib/src/foundation/binding.dart @@ -140,9 +140,9 @@ abstract class BindingBase { return true; }()); - assert(!_debugInitialized); + assert(_debugInitializedType == null); initInstances(); - assert(_debugInitialized); + assert(_debugInitializedType != null); assert(!_debugServiceExtensionsRegistered); initServiceExtensions(); @@ -154,7 +154,7 @@ abstract class BindingBase { } bool _debugConstructed = false; - static bool _debugInitialized = false; + static Type? _debugInitializedType; static bool _debugServiceExtensionsRegistered = false; /// Additional configuration used by the framework during hot reload. @@ -256,9 +256,9 @@ abstract class BindingBase { @protected @mustCallSuper void initInstances() { - assert(!_debugInitialized); + assert(_debugInitializedType == null); assert(() { - _debugInitialized = true; + _debugInitializedType = runtimeType; return true; }()); } @@ -277,7 +277,7 @@ abstract class BindingBase { @protected static T checkInstance(T? instance) { assert(() { - if (!_debugInitialized && instance == null) { + if (_debugInitializedType == null && instance == null) { throw FlutterError.fromParts([ ErrorSummary('Binding has not yet been initialized.'), ErrorDescription('The "instance" getter on the $T binding mixin is only available once that binding has been initialized.'), @@ -298,7 +298,7 @@ abstract class BindingBase { ]); } if (instance == null) { - assert(_debugInitialized); + assert(_debugInitializedType == null); throw FlutterError.fromParts([ ErrorSummary('Binding mixin instance is null but bindings are already initialized.'), ErrorDescription( @@ -315,11 +315,14 @@ abstract class BindingBase { 'It is also possible that $T does not implement "initInstances()" to assign a value to "instance". See the ' 'documentation of the BaseBinding class for more details.', ), + ErrorHint( + 'The binding that was initialized was of the type "$_debugInitializedType". ' + ), ]); } try { assert(instance != null); - if (instance._debugConstructed && !_debugInitialized) { + if (instance._debugConstructed && _debugInitializedType == null) { throw FlutterError.fromParts([ ErrorSummary('Binding initialized without calling initInstances.'), ErrorDescription('An instance of $T is non-null, but BindingBase.initInstances() has not yet been called.'), @@ -335,7 +338,7 @@ abstract class BindingBase { ]); } if (!instance._debugConstructed) { - // The state of _debugInitialized doesn't matter in this failure mode. + // The state of _debugInitializedType doesn't matter in this failure mode. throw FlutterError.fromParts([ ErrorSummary('Binding did not complete initialization.'), ErrorDescription('An instance of $T is non-null, but the BindingBase() constructor has not yet been called.'), @@ -361,6 +364,36 @@ abstract class BindingBase { return instance!; } + /// In debug builds, the type of the current binding, if any, or else null. + /// + /// This may be useful in asserts to verify that the binding has not been initialized + /// before the point in the application code that wants to initialize the binding, or + /// to verify that the binding is the one that is expected. + /// + /// For example, if an application uses [Zone]s to report uncaught execptions, it may + /// need to ensure that `ensureInitialized()` has not yet been invoked on any binding + /// at the point where it configures the zone and initializes the binding. + /// + /// If this returns null, the binding has not been initialized. + /// + /// If this returns a non-null value, it returns the type of the binding instance. + /// + /// To obtain the binding itself, consider the `instance` getter on the [BindingBase] + /// subclass or mixin. + /// + /// This method only returns a useful value in debug builds. In release builds, the + /// return value is always null; to improve startup performance, the type of the + /// binding is not tracked in release builds. + /// + /// See also: + /// + /// * [BindingBase], whose class documentation describes the conventions for dealing + /// with bindings. + /// * [initInstances], whose documentation details how to create a binding mixin. + static Type? debugBindingType() { + return _debugInitializedType; + } + /// Called when the binding is initialized, to register service /// extensions. /// diff --git a/packages/flutter/test/foundation/binding_test.dart b/packages/flutter/test/foundation/binding_test.dart new file mode 100644 index 00000000000..279038c4978 --- /dev/null +++ b/packages/flutter/test/foundation/binding_test.dart @@ -0,0 +1,37 @@ +// 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 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +// based on the sample code in foundation/binding.dart + +mixin FooBinding on BindingBase { + @override + void initInstances() { + super.initInstances(); + _instance = this; + } + + static FooBinding get instance => BindingBase.checkInstance(_instance); + static FooBinding? _instance; +} + +class FooLibraryBinding extends BindingBase with FooBinding { + static FooBinding ensureInitialized() { + if (FooBinding._instance == null) { + FooLibraryBinding(); + } + return FooBinding.instance; + } +} + + +void main() { + test('BindingBase.debugBindingType', () async { + expect(BindingBase.debugBindingType(), isNull); + FooLibraryBinding.ensureInitialized(); + expect(BindingBase.debugBindingType(), FooLibraryBinding); + }); +}