From 09b6672e77dc7ab4d4e4a8e70f54b2ed22faaae6 Mon Sep 17 00:00:00 2001 From: xubaolin Date: Wed, 27 Jul 2022 13:22:05 +0800 Subject: [PATCH] Do not crash when remove SelectableText during handle drag (#108369) --- .../lib/src/widgets/text_selection.dart | 12 ++++ .../test/widgets/selectable_text_test.dart | 68 +++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index f0d12c88d54..38691d5a9e3 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -444,6 +444,9 @@ class TextSelectionOverlay { late Offset _dragEndPosition; void _handleSelectionEndHandleDragStart(DragStartDetails details) { + if (!renderObject.attached) { + return; + } final Size handleSize = selectionControls!.getHandleSize( renderObject.preferredLineHeight, ); @@ -451,6 +454,9 @@ class TextSelectionOverlay { } void _handleSelectionEndHandleDragUpdate(DragUpdateDetails details) { + if (!renderObject.attached) { + return; + } _dragEndPosition += details.delta; final TextPosition position = renderObject.getPositionForPoint(_dragEndPosition); @@ -492,6 +498,9 @@ class TextSelectionOverlay { late Offset _dragStartPosition; void _handleSelectionStartHandleDragStart(DragStartDetails details) { + if (!renderObject.attached) { + return; + } final Size handleSize = selectionControls!.getHandleSize( renderObject.preferredLineHeight, ); @@ -499,6 +508,9 @@ class TextSelectionOverlay { } void _handleSelectionStartHandleDragUpdate(DragUpdateDetails details) { + if (!renderObject.attached) { + return; + } _dragStartPosition += details.delta; final TextPosition position = renderObject.getPositionForPoint(_dragStartPosition); diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index d1b6f35ef63..e53da5b7b9d 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -176,6 +176,74 @@ void main() { ); } + 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; + late StateSetter setter; + await tester.pumpWidget( + MaterialApp( + home: Material( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + setter = setState; + if (isShow) { + return const SelectableText( + 'abc def ghi', + dragStartBehavior: DragStartBehavior.down, + ); + } else { + return const SizedBox.shrink(); + } + }, + ), + ), + ), + ); + + final EditableText editableTextWidget = tester.widget(find.byType(EditableText)); + final TextEditingController controller = editableTextWidget.controller; + + // Long press the 'e' to select 'def'. + final Offset ePos = textOffsetToPosition(tester, 5); + TestGesture gesture = await tester.startGesture(ePos, pointer: 7); + await tester.pump(const Duration(seconds: 2)); + await gesture.up(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero + + final TextSelection selection = controller.selection; + expect(selection.baseOffset, 4); + expect(selection.extentOffset, 7); + + final RenderEditable renderEditable = findRenderEditable(tester); + final List endpoints = globalize( + renderEditable.getEndpointsForSelection(selection), + renderEditable, + ); + expect(endpoints.length, 2); + + // Drag the left handle to the left. + final Offset handlePos = endpoints[0].point + const Offset(-1.0, 1.0); + final Offset newHandlePos = textOffsetToPosition(tester, 1); + final Offset newHandlePos1 = textOffsetToPosition(tester, 0); + gesture = await tester.startGesture(handlePos, pointer: 7); + await tester.pump(); + await gesture.moveTo(newHandlePos); + await tester.pump(); + + // Unmount the SelectableText during handle drag + setter(() { + isShow = false; + }); + await tester.pump(); + + await gesture.moveTo(newHandlePos1); + await tester.pump(); // Do not crash here + + await gesture.up(); + await tester.pump(); + }); + testWidgets('has expected defaults', (WidgetTester tester) async { await tester.pumpWidget( boilerplate(