From 23c20d702badf669ac63e1777314da45419f46db Mon Sep 17 00:00:00 2001 From: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Tue, 23 Nov 2021 15:13:08 -0800 Subject: [PATCH] [TextInput] send `setEditingState` before `show` to the text input plugin when switching input clients (#92945) --- .../lib/src/widgets/editable_text.dart | 17 ++--- .../test/widgets/editable_text_test.dart | 73 ++++++++++++++++++- 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index 837a67ec93..685ca5b240 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -2207,16 +2207,9 @@ class EditableTextState extends State with AutomaticKeepAliveClien _textInputConnection = _needsAutofill && currentAutofillScope != null ? currentAutofillScope!.attach(this, _effectiveAutofillClient.textInputConfiguration) : TextInput.attach(this, _effectiveAutofillClient.textInputConfiguration); - _textInputConnection!.show(); _updateSizeAndTransform(); _updateComposingRectIfNeeded(); _updateCaretRectIfNeeded(); - if (_needsAutofill) { - // Request autofill AFTER the size and the transform have been sent to - // the platform text input plugin. - _textInputConnection!.requestAutofill(); - } - final TextStyle style = widget.style; _textInputConnection! ..setStyle( @@ -2226,8 +2219,14 @@ class EditableTextState extends State with AutomaticKeepAliveClien textDirection: _textDirection, textAlign: widget.textAlign, ) - ..setEditingState(localValue); - _lastKnownRemoteTextEditingValue = localValue; + ..setEditingState(localValue) + ..show(); + if (_needsAutofill) { + // Request autofill AFTER the size and the transform have been sent to + // the platform text input plugin. + _textInputConnection!.requestAutofill(); + } + _lastKnownRemoteTextEditingValue = localValue; } else { _textInputConnection!.show(); } diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index 212504fb78..ee3f7c1854 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -6619,14 +6619,18 @@ void main() { )); await tester.showKeyboard(find.byType(EditableText)); - // TextInput.show should be before TextInput.setEditingState + // TextInput.show should be after TextInput.setEditingState. + // On Android setEditingState triggers an IME restart which may prevent + // the keyboard from showing if the show keyboard request comes before the + // restart. + // See: https://github.com/flutter/flutter/issues/68571. final List logOrder = [ 'TextInput.setClient', - 'TextInput.show', 'TextInput.setEditableSizeAndTransform', 'TextInput.setMarkedTextRect', 'TextInput.setStyle', 'TextInput.setEditingState', + 'TextInput.show', 'TextInput.setEditingState', 'TextInput.show', 'TextInput.setCaretRect', @@ -6637,6 +6641,67 @@ void main() { ); }); + testWidgets( + 'keyboard is requested after setEditingState after switching to a new text field', + (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/68571. + final EditableText editableText1 = EditableText( + showSelectionHandles: true, + maxLines: 2, + controller: TextEditingController(), + focusNode: FocusNode(), + cursorColor: Colors.red, + backgroundCursorColor: Colors.blue, + style: Typography.material2018().black.subtitle1!.copyWith(fontFamily: 'Roboto'), + keyboardType: TextInputType.text, + ); + + final EditableText editableText2 = EditableText( + showSelectionHandles: true, + maxLines: 2, + controller: TextEditingController(), + focusNode: FocusNode(), + cursorColor: Colors.red, + backgroundCursorColor: Colors.blue, + style: Typography.material2018().black.subtitle1!.copyWith(fontFamily: 'Roboto'), + keyboardType: TextInputType.text, + ); + + await tester.pumpWidget(MaterialApp( + home: Center( + child: Column( + children: [editableText1, editableText2], + ), + ), + )); + + await tester.tap(find.byWidget(editableText1)); + await tester.pumpAndSettle(); + + tester.testTextInput.log.clear(); + await tester.tap(find.byWidget(editableText2)); + await tester.pumpAndSettle(); + + // Send TextInput.show after TextInput.setEditingState. Otherwise + // some Android keyboards ignore the "show keyboard" request, as the + // Android text input plugin restarts the input method when setEditingState + // is sent by the framework. + final List logOrder = [ + 'TextInput.clearClient', + 'TextInput.setClient', + 'TextInput.setEditableSizeAndTransform', + 'TextInput.setMarkedTextRect', + 'TextInput.setStyle', + 'TextInput.setEditingState', + 'TextInput.show', + 'TextInput.setCaretRect', + ]; + expect( + tester.testTextInput.log.map((MethodCall m) => m.method), + logOrder, + ); + }); + testWidgets('setEditingState is not called when text changes', (WidgetTester tester) async { // We shouldn't get a message here because this change is owned by the platform side. const String testText = 'flutter is the best!'; @@ -6666,11 +6731,11 @@ void main() { final List logOrder = [ 'TextInput.setClient', - 'TextInput.show', 'TextInput.setEditableSizeAndTransform', 'TextInput.setMarkedTextRect', 'TextInput.setStyle', 'TextInput.setEditingState', + 'TextInput.show', 'TextInput.setEditingState', 'TextInput.show', 'TextInput.setCaretRect', @@ -6716,11 +6781,11 @@ void main() { final List logOrder = [ 'TextInput.setClient', - 'TextInput.show', 'TextInput.setEditableSizeAndTransform', 'TextInput.setMarkedTextRect', 'TextInput.setStyle', 'TextInput.setEditingState', + 'TextInput.show', 'TextInput.setEditingState', 'TextInput.show', 'TextInput.setCaretRect',