From dbc5792a7e51368af4ee08700ecf6569afba5792 Mon Sep 17 00:00:00 2001 From: xubaolin Date: Thu, 28 Jul 2022 11:46:35 +0800 Subject: [PATCH 1/3] Move the debug statement to the scope of the assertion. --- .../lib/src/widgets/text_selection.dart | 26 ++++++++----- .../test/widgets/selectable_text_test.dart | 37 +++++++++++++++++++ 2 files changed, 54 insertions(+), 9 deletions(-) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 59e6df422893e..f652b0c7e61a4 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -616,15 +616,23 @@ class SelectionOverlay { _endHandleType = endHandleType, _lineHeightAtEnd = lineHeightAtEnd, _selectionEndpoints = selectionEndpoints, - _toolbarLocation = toolbarLocation { - final OverlayState? overlay = Overlay.of(context, rootOverlay: true); - assert( - overlay != null, - 'No Overlay widget exists above $context.\n' - 'Usually the Navigator created by WidgetsApp provides the overlay. Perhaps your ' - 'app content was created above the Navigator with the WidgetsApp builder parameter.', - ); - } + _toolbarLocation = toolbarLocation, + assert(() { + final OverlayState? overlay = Overlay.of(context, rootOverlay: true); + if (overlay == null) { + final List information = [ + ErrorSummary('No Overlay widget found.'), + ErrorDescription( + 'No Overlay widget exists above $context.\n' + 'Usually the Navigator created by WidgetsApp provides the overlay. Perhaps your ' + 'app content was created above the Navigator with the WidgetsApp builder parameter.' + ), + ]; + + throw FlutterError.fromParts(information); + } + return true; + }()); /// The context in which the selection handles should appear. /// diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index e53da5b7b9db3..af9ee9de4e909 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -176,6 +176,43 @@ void main() { ); } + testWidgets('throw if no Overlay widget exists above', (WidgetTester tester) async { + final FlutterExceptionHandler? handler = FlutterError.onError; + final List errors = []; + FlutterError.onError = (FlutterErrorDetails details) { + errors.add(details); + }; + + await tester.pumpWidget( + const Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: MediaQueryData(size: Size(800.0, 600.0)), + child: Center( + child: Material( + child: SelectableText('I love Flutter!'), + ), + ), + ), + ), + ); + + final Offset textFieldStart = tester.getTopLeft(find.byType(SelectableText)); + final TestGesture gesture = await tester.startGesture(textFieldStart, kind: PointerDeviceKind.mouse); + await tester.pump(const Duration(seconds: 2)); + await gesture.up(); + await tester.pumpAndSettle(); + + await tester.pumpWidget(const SizedBox.shrink()); + FlutterError.onError = handler; + + final FlutterError error = errors[0].exception as FlutterError; + expect( + error.message, + contains('No Overlay widget exists above EditableText'), + ); + }); + testWidgets('Do not crash when remove SelectableText during handle drag', (WidgetTester tester) async { // Regression test https://github.com/flutter/flutter/issues/108242 bool isShow = true; From c80c27045d68454adfa7c8e55aadb22367392759 Mon Sep 17 00:00:00 2001 From: xubaolin Date: Fri, 29 Jul 2022 08:59:41 +0800 Subject: [PATCH 2/3] feedback --- .../flutter/test/widgets/selectable_text_test.dart | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index af9ee9de4e909..6bccf57e42a99 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -177,12 +177,6 @@ void main() { } testWidgets('throw if no Overlay widget exists above', (WidgetTester tester) async { - final FlutterExceptionHandler? handler = FlutterError.onError; - final List errors = []; - FlutterError.onError = (FlutterErrorDetails details) { - errors.add(details); - }; - await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, @@ -203,14 +197,14 @@ void main() { await gesture.up(); await tester.pumpAndSettle(); - await tester.pumpWidget(const SizedBox.shrink()); - FlutterError.onError = handler; - - final FlutterError error = errors[0].exception as FlutterError; + final FlutterError error = tester.takeException() as FlutterError; expect( error.message, contains('No Overlay widget exists above EditableText'), ); + + await tester.pumpWidget(const SizedBox.shrink()); + expect(tester.takeException(), isNotNull); // side effect exception }); testWidgets('Do not crash when remove SelectableText during handle drag', (WidgetTester tester) async { From 672c7034441d00e136ce209f7fbc4abdabc0f36f Mon Sep 17 00:00:00 2001 From: xubaolin Date: Mon, 1 Aug 2022 11:18:46 +0800 Subject: [PATCH 3/3] feedback --- .../api/lib/widgets/overlay/overlay.0.dart | 2 +- .../lib/src/cupertino/context_menu.dart | 2 +- .../src/material/bottom_navigation_bar.dart | 2 +- .../flutter/lib/src/material/range_slider.dart | 2 +- packages/flutter/lib/src/material/slider.dart | 2 +- packages/flutter/lib/src/material/tooltip.dart | 2 +- .../flutter/lib/src/widgets/autocomplete.dart | 2 +- .../flutter/lib/src/widgets/drag_target.dart | 3 ++- .../lib/src/widgets/reorderable_list.dart | 6 +++--- .../lib/src/widgets/selectable_region.dart | 3 ++- .../lib/src/widgets/text_selection.dart | 18 ++---------------- .../test/widgets/selectable_text_test.dart | 2 +- 12 files changed, 17 insertions(+), 29 deletions(-) diff --git a/examples/api/lib/widgets/overlay/overlay.0.dart b/examples/api/lib/widgets/overlay/overlay.0.dart index b90d0f0928b77..c49ce87c8e18c 100644 --- a/examples/api/lib/widgets/overlay/overlay.0.dart +++ b/examples/api/lib/widgets/overlay/overlay.0.dart @@ -132,7 +132,7 @@ class _OverlayExampleState extends State { ); // Add the OverlayEntry to the Overlay. - Overlay.of(context)!.insert(overlayEntry!); + Overlay.of(context, debugRequiredFor: widget)!.insert(overlayEntry!); } // Remove the OverlayEntry. diff --git a/packages/flutter/lib/src/cupertino/context_menu.dart b/packages/flutter/lib/src/cupertino/context_menu.dart index 9dc484033eeff..39216fc56aa1d 100644 --- a/packages/flutter/lib/src/cupertino/context_menu.dart +++ b/packages/flutter/lib/src/cupertino/context_menu.dart @@ -363,7 +363,7 @@ class _CupertinoContextMenuState extends State with Ticker ); }, ); - Overlay.of(context, rootOverlay: true)!.insert(_lastOverlayEntry!); + Overlay.of(context, rootOverlay: true, debugRequiredFor: widget)!.insert(_lastOverlayEntry!); _openController.forward(); } diff --git a/packages/flutter/lib/src/material/bottom_navigation_bar.dart b/packages/flutter/lib/src/material/bottom_navigation_bar.dart index 5635c561f32e0..656b74041fda7 100644 --- a/packages/flutter/lib/src/material/bottom_navigation_bar.dart +++ b/packages/flutter/lib/src/material/bottom_navigation_bar.dart @@ -1004,7 +1004,7 @@ class _BottomNavigationBarState extends State with TickerPr assert(debugCheckHasDirectionality(context)); assert(debugCheckHasMaterialLocalizations(context)); assert(debugCheckHasMediaQuery(context)); - assert(Overlay.of(context, debugRequiredFor: widget) != null); + assert(debugCheckHasOverlay(context)); final BottomNavigationBarThemeData bottomTheme = BottomNavigationBarTheme.of(context); final BottomNavigationBarLandscapeLayout layout = widget.landscapeLayout diff --git a/packages/flutter/lib/src/material/range_slider.dart b/packages/flutter/lib/src/material/range_slider.dart index 48cf5ad4b6bf4..df6839f3f457f 100644 --- a/packages/flutter/lib/src/material/range_slider.dart +++ b/packages/flutter/lib/src/material/range_slider.dart @@ -655,7 +655,7 @@ class _RangeSliderState extends State with TickerProviderStateMixin ); }, ); - Overlay.of(context)!.insert(overlayEntry!); + Overlay.of(context, debugRequiredFor: widget)!.insert(overlayEntry!); } } } diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index 1a5b1ab36fae6..04c761b0b6a4c 100644 --- a/packages/flutter/lib/src/material/slider.dart +++ b/packages/flutter/lib/src/material/slider.dart @@ -842,7 +842,7 @@ class _SliderState extends State with TickerProviderStateMixin { ); }, ); - Overlay.of(context)!.insert(overlayEntry!); + Overlay.of(context, debugRequiredFor: widget)!.insert(overlayEntry!); } } } diff --git a/packages/flutter/lib/src/material/tooltip.dart b/packages/flutter/lib/src/material/tooltip.dart index 698619ee9c823..f5853f0d798f3 100644 --- a/packages/flutter/lib/src/material/tooltip.dart +++ b/packages/flutter/lib/src/material/tooltip.dart @@ -698,7 +698,7 @@ class TooltipState extends State with SingleTickerProviderStateMixin { if (_tooltipMessage.isEmpty) { return widget.child ?? const SizedBox(); } - assert(Overlay.of(context, debugRequiredFor: widget) != null); + assert(debugCheckHasOverlay(context)); final ThemeData theme = Theme.of(context); final TooltipThemeData tooltipTheme = TooltipTheme.of(context); final TextStyle defaultTextStyle; diff --git a/packages/flutter/lib/src/widgets/autocomplete.dart b/packages/flutter/lib/src/widgets/autocomplete.dart index c78f7d0d5a98e..197e657e60fd7 100644 --- a/packages/flutter/lib/src/widgets/autocomplete.dart +++ b/packages/flutter/lib/src/widgets/autocomplete.dart @@ -432,7 +432,7 @@ class _RawAutocompleteState extends State> ); }, ); - Overlay.of(context, rootOverlay: true)!.insert(newFloatingOptions); + Overlay.of(context, rootOverlay: true, debugRequiredFor: widget)!.insert(newFloatingOptions); _floatingOptions = newFloatingOptions; } else { _floatingOptions = null; diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index 9da861b0ac850..17a685960dcea 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -9,6 +9,7 @@ import 'package:flutter/services.dart'; import 'basic.dart'; import 'binding.dart'; +import 'debug.dart'; import 'framework.dart'; import 'media_query.dart'; import 'overlay.dart'; @@ -598,7 +599,7 @@ class _DraggableState extends State> { @override Widget build(BuildContext context) { - assert(Overlay.of(context, debugRequiredFor: widget, rootOverlay: widget.rootOverlay) != null); + assert(debugCheckHasOverlay(context)); final bool canDrag = widget.maxSimultaneousDrags == null || _activeCount < widget.maxSimultaneousDrags!; final bool showChild = _activeCount == 0 || widget.childWhenDragging == null; diff --git a/packages/flutter/lib/src/widgets/reorderable_list.dart b/packages/flutter/lib/src/widgets/reorderable_list.dart index a7261bb34264a..9cd2c3830271e 100644 --- a/packages/flutter/lib/src/widgets/reorderable_list.dart +++ b/packages/flutter/lib/src/widgets/reorderable_list.dart @@ -702,7 +702,7 @@ class SliverReorderableListState extends State with Ticke ); _dragInfo!.startDrag(); - final OverlayState overlay = Overlay.of(context)!; + final OverlayState overlay = Overlay.of(context, debugRequiredFor: widget)!; assert(_overlayEntry == null); _overlayEntry = OverlayEntry(builder: _dragInfo!.createProxy); overlay.insert(_overlayEntry!); @@ -897,7 +897,7 @@ class SliverReorderableListState extends State with Ticke } final Widget child = widget.itemBuilder(context, index); assert(child.key != null, 'All list items must have a key'); - final OverlayState overlay = Overlay.of(context)!; + final OverlayState overlay = Overlay.of(context, debugRequiredFor: widget)!; return _ReorderableItem( key: _ReorderableItemGlobalKey(child.key!, index, this), index: index, @@ -1284,7 +1284,7 @@ class _DragInfo extends Drag { } Offset _overlayOrigin(BuildContext context) { - final OverlayState overlay = Overlay.of(context)!; + final OverlayState overlay = Overlay.of(context, debugRequiredFor: context.widget)!; final RenderBox overlayBox = overlay.context.findRenderObject()! as RenderBox; return overlayBox.localToGlobal(Offset.zero); } diff --git a/packages/flutter/lib/src/widgets/selectable_region.dart b/packages/flutter/lib/src/widgets/selectable_region.dart index d0681872f202a..e34543df94117 100644 --- a/packages/flutter/lib/src/widgets/selectable_region.dart +++ b/packages/flutter/lib/src/widgets/selectable_region.dart @@ -12,6 +12,7 @@ import 'package:flutter/services.dart'; import 'actions.dart'; import 'basic.dart'; +import 'debug.dart'; import 'focus_manager.dart'; import 'focus_scope.dart'; import 'framework.dart'; @@ -804,7 +805,7 @@ class _SelectableRegionState extends State with TextSelectionD @override Widget build(BuildContext context) { - assert(Overlay.of(context, debugRequiredFor: widget) != null); + assert(debugCheckHasOverlay(context)); return CompositedTransformTarget( link: _toolbarLayerLink, child: RawGestureDetector( diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index f652b0c7e61a4..79b702bfc15c0 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -16,6 +16,7 @@ import 'basic.dart'; import 'binding.dart'; import 'constants.dart'; import 'container.dart'; +import 'debug.dart'; import 'editable_text.dart'; import 'framework.dart'; import 'gesture_detector.dart'; @@ -617,22 +618,7 @@ class SelectionOverlay { _lineHeightAtEnd = lineHeightAtEnd, _selectionEndpoints = selectionEndpoints, _toolbarLocation = toolbarLocation, - assert(() { - final OverlayState? overlay = Overlay.of(context, rootOverlay: true); - if (overlay == null) { - final List information = [ - ErrorSummary('No Overlay widget found.'), - ErrorDescription( - 'No Overlay widget exists above $context.\n' - 'Usually the Navigator created by WidgetsApp provides the overlay. Perhaps your ' - 'app content was created above the Navigator with the WidgetsApp builder parameter.' - ), - ]; - - throw FlutterError.fromParts(information); - } - return true; - }()); + assert(debugCheckHasOverlay(context)); /// The context in which the selection handles should appear. /// diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index 6bccf57e42a99..ba31a8974d7c8 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -200,7 +200,7 @@ void main() { final FlutterError error = tester.takeException() as FlutterError; expect( error.message, - contains('No Overlay widget exists above EditableText'), + contains('EditableText widgets require an Overlay widget ancestor'), ); await tester.pumpWidget(const SizedBox.shrink());