Skip to content

Commit fce2f93

Browse files
authored
Add a BindingBase.debugBindingType() method to enable asserts that want to verify that the binding isn't initialized (#98226)
1 parent eac1a54 commit fce2f93

File tree

2 files changed

+79
-9
lines changed

2 files changed

+79
-9
lines changed

packages/flutter/lib/src/foundation/binding.dart

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,9 @@ abstract class BindingBase {
140140
return true;
141141
}());
142142

143-
assert(!_debugInitialized);
143+
assert(_debugInitializedType == null);
144144
initInstances();
145-
assert(_debugInitialized);
145+
assert(_debugInitializedType != null);
146146

147147
assert(!_debugServiceExtensionsRegistered);
148148
initServiceExtensions();
@@ -154,7 +154,7 @@ abstract class BindingBase {
154154
}
155155

156156
bool _debugConstructed = false;
157-
static bool _debugInitialized = false;
157+
static Type? _debugInitializedType;
158158
static bool _debugServiceExtensionsRegistered = false;
159159

160160
/// Additional configuration used by the framework during hot reload.
@@ -256,9 +256,9 @@ abstract class BindingBase {
256256
@protected
257257
@mustCallSuper
258258
void initInstances() {
259-
assert(!_debugInitialized);
259+
assert(_debugInitializedType == null);
260260
assert(() {
261-
_debugInitialized = true;
261+
_debugInitializedType = runtimeType;
262262
return true;
263263
}());
264264
}
@@ -277,7 +277,7 @@ abstract class BindingBase {
277277
@protected
278278
static T checkInstance<T extends BindingBase>(T? instance) {
279279
assert(() {
280-
if (!_debugInitialized && instance == null) {
280+
if (_debugInitializedType == null && instance == null) {
281281
throw FlutterError.fromParts(<DiagnosticsNode>[
282282
ErrorSummary('Binding has not yet been initialized.'),
283283
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 {
298298
]);
299299
}
300300
if (instance == null) {
301-
assert(_debugInitialized);
301+
assert(_debugInitializedType == null);
302302
throw FlutterError.fromParts(<DiagnosticsNode>[
303303
ErrorSummary('Binding mixin instance is null but bindings are already initialized.'),
304304
ErrorDescription(
@@ -315,11 +315,14 @@ abstract class BindingBase {
315315
'It is also possible that $T does not implement "initInstances()" to assign a value to "instance". See the '
316316
'documentation of the BaseBinding class for more details.',
317317
),
318+
ErrorHint(
319+
'The binding that was initialized was of the type "$_debugInitializedType". '
320+
),
318321
]);
319322
}
320323
try {
321324
assert(instance != null);
322-
if (instance._debugConstructed && !_debugInitialized) {
325+
if (instance._debugConstructed && _debugInitializedType == null) {
323326
throw FlutterError.fromParts(<DiagnosticsNode>[
324327
ErrorSummary('Binding initialized without calling initInstances.'),
325328
ErrorDescription('An instance of $T is non-null, but BindingBase.initInstances() has not yet been called.'),
@@ -335,7 +338,7 @@ abstract class BindingBase {
335338
]);
336339
}
337340
if (!instance._debugConstructed) {
338-
// The state of _debugInitialized doesn't matter in this failure mode.
341+
// The state of _debugInitializedType doesn't matter in this failure mode.
339342
throw FlutterError.fromParts(<DiagnosticsNode>[
340343
ErrorSummary('Binding did not complete initialization.'),
341344
ErrorDescription('An instance of $T is non-null, but the BindingBase() constructor has not yet been called.'),
@@ -361,6 +364,36 @@ abstract class BindingBase {
361364
return instance!;
362365
}
363366

367+
/// In debug builds, the type of the current binding, if any, or else null.
368+
///
369+
/// This may be useful in asserts to verify that the binding has not been initialized
370+
/// before the point in the application code that wants to initialize the binding, or
371+
/// to verify that the binding is the one that is expected.
372+
///
373+
/// For example, if an application uses [Zone]s to report uncaught execptions, it may
374+
/// need to ensure that `ensureInitialized()` has not yet been invoked on any binding
375+
/// at the point where it configures the zone and initializes the binding.
376+
///
377+
/// If this returns null, the binding has not been initialized.
378+
///
379+
/// If this returns a non-null value, it returns the type of the binding instance.
380+
///
381+
/// To obtain the binding itself, consider the `instance` getter on the [BindingBase]
382+
/// subclass or mixin.
383+
///
384+
/// This method only returns a useful value in debug builds. In release builds, the
385+
/// return value is always null; to improve startup performance, the type of the
386+
/// binding is not tracked in release builds.
387+
///
388+
/// See also:
389+
///
390+
/// * [BindingBase], whose class documentation describes the conventions for dealing
391+
/// with bindings.
392+
/// * [initInstances], whose documentation details how to create a binding mixin.
393+
static Type? debugBindingType() {
394+
return _debugInitializedType;
395+
}
396+
364397
/// Called when the binding is initialized, to register service
365398
/// extensions.
366399
///
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
import 'package:flutter/foundation.dart';
6+
import 'package:flutter_test/flutter_test.dart';
7+
8+
// based on the sample code in foundation/binding.dart
9+
10+
mixin FooBinding on BindingBase {
11+
@override
12+
void initInstances() {
13+
super.initInstances();
14+
_instance = this;
15+
}
16+
17+
static FooBinding get instance => BindingBase.checkInstance(_instance);
18+
static FooBinding? _instance;
19+
}
20+
21+
class FooLibraryBinding extends BindingBase with FooBinding {
22+
static FooBinding ensureInitialized() {
23+
if (FooBinding._instance == null) {
24+
FooLibraryBinding();
25+
}
26+
return FooBinding.instance;
27+
}
28+
}
29+
30+
31+
void main() {
32+
test('BindingBase.debugBindingType', () async {
33+
expect(BindingBase.debugBindingType(), isNull);
34+
FooLibraryBinding.ensureInitialized();
35+
expect(BindingBase.debugBindingType(), FooLibraryBinding);
36+
});
37+
}

0 commit comments

Comments
 (0)