Add more info to OverlayState.insert
error messages (#129363)
I was debugging an Overlay issue and felt I could have identified the problem faster if the existing assertions provided more information about the current state of the OverlayEntry and Overlay.
This commit is contained in:
parent
0bc5a2bca4
commit
5de6684b9c
@ -222,7 +222,7 @@ class OverlayEntry implements Listenable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => '${describeIdentity(this)}(opaque: $opaque; maintainState: $maintainState)';
|
String toString() => '${describeIdentity(this)}(opaque: $opaque; maintainState: $maintainState)${_disposedByOwner ? "(DISPOSED)" : ""}';
|
||||||
}
|
}
|
||||||
|
|
||||||
class _OverlayEntryWidget extends StatefulWidget {
|
class _OverlayEntryWidget extends StatefulWidget {
|
||||||
@ -296,7 +296,7 @@ class _OverlayEntryWidgetState extends State<_OverlayEntryWidget> {
|
|||||||
late final Iterable<RenderBox> _hitTestOrderIterable = _createChildIterable(reversed: true);
|
late final Iterable<RenderBox> _hitTestOrderIterable = _createChildIterable(reversed: true);
|
||||||
|
|
||||||
// The following uses sync* because hit-testing is lazy, and LinkedList as a
|
// The following uses sync* because hit-testing is lazy, and LinkedList as a
|
||||||
// Iterable doesn't support current modification.
|
// Iterable doesn't support concurrent modification.
|
||||||
Iterable<RenderBox> _createChildIterable({ required bool reversed }) sync* {
|
Iterable<RenderBox> _createChildIterable({ required bool reversed }) sync* {
|
||||||
final LinkedList<_OverlayEntryLocation>? children = _sortedTheaterSiblings;
|
final LinkedList<_OverlayEntryLocation>? children = _sortedTheaterSiblings;
|
||||||
if (children == null || children.isEmpty) {
|
if (children == null || children.isEmpty) {
|
||||||
@ -543,6 +543,55 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin {
|
|||||||
return _entries.length;
|
return _entries.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _debugCanInsertEntry(OverlayEntry entry) {
|
||||||
|
final List<DiagnosticsNode> operandsInformation = <DiagnosticsNode>[
|
||||||
|
DiagnosticsProperty<OverlayEntry>('The OverlayEntry was', entry, style: DiagnosticsTreeStyle.errorProperty),
|
||||||
|
DiagnosticsProperty<OverlayState>(
|
||||||
|
'The Overlay the OverlayEntry was trying to insert to was', this, style: DiagnosticsTreeStyle.errorProperty,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!mounted) {
|
||||||
|
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||||
|
ErrorSummary('Attempted to insert an OverlayEntry to an already disposed Overlay.'),
|
||||||
|
...operandsInformation,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
final OverlayState? currentOverlay = entry._overlay;
|
||||||
|
final bool alreadyContainsEntry = _entries.contains(entry);
|
||||||
|
|
||||||
|
if (alreadyContainsEntry) {
|
||||||
|
final bool inconsistentOverlayState = !identical(currentOverlay, this);
|
||||||
|
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||||
|
ErrorSummary('The specified entry is already present in the target Overlay.'),
|
||||||
|
...operandsInformation,
|
||||||
|
if (inconsistentOverlayState) ErrorHint('This could be an error in the Flutter framework.')
|
||||||
|
else ErrorHint(
|
||||||
|
'Consider calling remove on the OverlayEntry before inserting it to a different Overlay, '
|
||||||
|
'or switching to the OverlayPortal API to avoid manual OverlayEntry management.'
|
||||||
|
),
|
||||||
|
if (inconsistentOverlayState) DiagnosticsProperty<OverlayState>(
|
||||||
|
"The OverlayEntry's current Overlay was", currentOverlay, style: DiagnosticsTreeStyle.errorProperty,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentOverlay == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw FlutterError.fromParts(<DiagnosticsNode>[
|
||||||
|
ErrorSummary('The specified entry is already present in a different Overlay.'),
|
||||||
|
...operandsInformation,
|
||||||
|
DiagnosticsProperty<OverlayState>("The OverlayEntry's current Overlay was", currentOverlay, style: DiagnosticsTreeStyle.errorProperty,),
|
||||||
|
ErrorHint(
|
||||||
|
'Consider calling remove on the OverlayEntry before inserting it to a different Overlay, '
|
||||||
|
'or switching to the OverlayPortal API to avoid manual OverlayEntry management.'
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/// Insert the given entry into the overlay.
|
/// Insert the given entry into the overlay.
|
||||||
///
|
///
|
||||||
/// If `below` is non-null, the entry is inserted just below `below`.
|
/// If `below` is non-null, the entry is inserted just below `below`.
|
||||||
@ -552,8 +601,7 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin {
|
|||||||
/// It is an error to specify both `above` and `below`.
|
/// It is an error to specify both `above` and `below`.
|
||||||
void insert(OverlayEntry entry, { OverlayEntry? below, OverlayEntry? above }) {
|
void insert(OverlayEntry entry, { OverlayEntry? below, OverlayEntry? above }) {
|
||||||
assert(_debugVerifyInsertPosition(above, below));
|
assert(_debugVerifyInsertPosition(above, below));
|
||||||
assert(!_entries.contains(entry), 'The specified entry is already present in the Overlay.');
|
assert(_debugCanInsertEntry(entry));
|
||||||
assert(entry._overlay == null, 'The specified entry is already present in another Overlay.');
|
|
||||||
entry._overlay = this;
|
entry._overlay = this;
|
||||||
setState(() {
|
setState(() {
|
||||||
_entries.insert(_insertionIndex(below, above), entry);
|
_entries.insert(_insertionIndex(below, above), entry);
|
||||||
@ -569,14 +617,7 @@ class OverlayState extends State<Overlay> with TickerProviderStateMixin {
|
|||||||
/// It is an error to specify both `above` and `below`.
|
/// It is an error to specify both `above` and `below`.
|
||||||
void insertAll(Iterable<OverlayEntry> entries, { OverlayEntry? below, OverlayEntry? above }) {
|
void insertAll(Iterable<OverlayEntry> entries, { OverlayEntry? below, OverlayEntry? above }) {
|
||||||
assert(_debugVerifyInsertPosition(above, below));
|
assert(_debugVerifyInsertPosition(above, below));
|
||||||
assert(
|
assert(entries.every(_debugCanInsertEntry));
|
||||||
entries.every((OverlayEntry entry) => !_entries.contains(entry)),
|
|
||||||
'One or more of the specified entries are already present in the Overlay.',
|
|
||||||
);
|
|
||||||
assert(
|
|
||||||
entries.every((OverlayEntry entry) => entry._overlay == null),
|
|
||||||
'One or more of the specified entries are already present in another Overlay.',
|
|
||||||
);
|
|
||||||
if (entries.isEmpty) {
|
if (entries.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1139,6 +1139,72 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('OverlayEntry throws if inserted to an invalid Overlay', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: Overlay(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final OverlayState overlay = tester.state(find.byType(Overlay));
|
||||||
|
final OverlayEntry entry = OverlayEntry(builder: (BuildContext context) => const SizedBox());
|
||||||
|
expect(
|
||||||
|
() => overlay.insert(entry),
|
||||||
|
returnsNormally,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Throws when inserted to the same Overlay.
|
||||||
|
expect(
|
||||||
|
() => overlay.insert(entry),
|
||||||
|
throwsA(isA<FlutterError>().having(
|
||||||
|
(FlutterError error) => error.toString(),
|
||||||
|
'toString()',
|
||||||
|
allOf(
|
||||||
|
contains('The specified entry is already present in the target Overlay.'),
|
||||||
|
contains('The OverlayEntry was'),
|
||||||
|
contains('The Overlay the OverlayEntry was trying to insert to was'),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: SizedBox(child: Overlay()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Throws if inserted to an already disposed Overlay.
|
||||||
|
expect(
|
||||||
|
() => overlay.insert(entry),
|
||||||
|
throwsA(isA<FlutterError>().having(
|
||||||
|
(FlutterError error) => error.toString(),
|
||||||
|
'toString()',
|
||||||
|
allOf(
|
||||||
|
contains('Attempted to insert an OverlayEntry to an already disposed Overlay.'),
|
||||||
|
contains('The OverlayEntry was'),
|
||||||
|
contains('The Overlay the OverlayEntry was trying to insert to was'),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
final OverlayState newOverlay = tester.state(find.byType(Overlay));
|
||||||
|
// Throws when inserted to a different Overlay without calling remove.
|
||||||
|
expect(
|
||||||
|
() => newOverlay.insert(entry),
|
||||||
|
throwsA(isA<FlutterError>().having(
|
||||||
|
(FlutterError error) => error.toString(),
|
||||||
|
'toString()',
|
||||||
|
allOf(
|
||||||
|
contains('The specified entry is already present in a different Overlay.'),
|
||||||
|
contains('The OverlayEntry was'),
|
||||||
|
contains('The Overlay the OverlayEntry was trying to insert to was'),
|
||||||
|
contains("The OverlayEntry's current Overlay was"),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
group('OverlayEntry listenable', () {
|
group('OverlayEntry listenable', () {
|
||||||
final GlobalKey overlayKey = GlobalKey();
|
final GlobalKey overlayKey = GlobalKey();
|
||||||
final Widget emptyOverlay = Directionality(
|
final Widget emptyOverlay = Directionality(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user