TextField should update IME when controller changes (#9261)
Fixes #9246
diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart
index 5ffec58..f8b4c82 100644
--- a/packages/flutter/lib/src/widgets/editable_text.dart
+++ b/packages/flutter/lib/src/widgets/editable_text.dart
@@ -239,9 +239,8 @@
if (config.controller != oldConfig.controller) {
oldConfig.controller.removeListener(_didChangeTextEditingValue);
config.controller.addListener(_didChangeTextEditingValue);
- if (_hasInputConnection && config.controller.value != oldConfig.controller.value)
- _textInputConnection.setEditingState(config.controller.value);
- }
+ _updateRemoteEditingValueIfNeeded();
+ }
if (config.focusNode != oldConfig.focusNode) {
oldConfig.focusNode.removeListener(_handleFocusChanged);
config.focusNode.addListener(_handleFocusChanged);
@@ -263,10 +262,13 @@
// TextInputClient implementation:
+ TextEditingValue _lastKnownRemoteTextEditingValue;
+
@override
void updateEditingValue(TextEditingValue value) {
if (value.text != _value.text)
_hideSelectionOverlayIfNeeded();
+ _lastKnownRemoteTextEditingValue = value;
_value = value;
if (config.onChanged != null)
config.onChanged(value.text);
@@ -280,6 +282,16 @@
config.onSubmitted(_value.text);
}
+ void _updateRemoteEditingValueIfNeeded() {
+ if (!_hasInputConnection)
+ return;
+ final TextEditingValue localValue = _value;
+ if (localValue == _lastKnownRemoteTextEditingValue)
+ return;
+ _lastKnownRemoteTextEditingValue = localValue;
+ _textInputConnection.setEditingState(localValue);
+ }
+
TextEditingValue get _value => config.controller.value;
set _value(TextEditingValue value) {
config.controller.value = value;
@@ -306,8 +318,10 @@
void _openInputConnectionIfNeeded() {
if (!_hasInputConnection) {
+ final TextEditingValue localValue = _value;
+ _lastKnownRemoteTextEditingValue = localValue;
_textInputConnection = TextInput.attach(this, new TextInputConfiguration(inputType: config.keyboardType))
- ..setEditingState(_value)
+ ..setEditingState(localValue)
..show();
}
}
@@ -316,6 +330,7 @@
if (_hasInputConnection) {
_textInputConnection.close();
_textInputConnection = null;
+ _lastKnownRemoteTextEditingValue = null;
}
}
@@ -429,6 +444,7 @@
}
void _didChangeTextEditingValue() {
+ _updateRemoteEditingValueIfNeeded();
_startOrStopCursorTimerIfNeeded();
_updateOrDisposeSelectionOverlayIfNeeded();
// TODO(abarth): Teach RenderEditable about ValueNotifier<TextEditingValue>
diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart
index 7609d12..4cf3486 100644
--- a/packages/flutter/test/material/text_field_test.dart
+++ b/packages/flutter/test/material/text_field_test.dart
@@ -886,4 +886,59 @@
expect(topLeft.x, equals(399.0));
});
+
+ testWidgets('Controller can update server', (WidgetTester tester) async {
+ final TextEditingController controller = new TextEditingController(
+ text: 'Initial Text',
+ );
+ final TextEditingController controller2 = new TextEditingController(
+ text: 'More Text',
+ );
+
+ TextEditingController currentController = controller;
+ StateSetter setState;
+
+ await tester.pumpWidget(
+ overlay(new Center(
+ child: new Material(
+ child: new StatefulBuilder(
+ builder: (BuildContext context, StateSetter setter) {
+ setState = setter;
+ return new TextField(controller: currentController);
+ }
+ ),
+ ),
+ ),
+ ));
+
+ expect(tester.testTextInput.editingState['text'], isEmpty);
+
+ await tester.tap(find.byType(TextField));
+ await tester.pump();
+
+ expect(tester.testTextInput.editingState['text'], equals('Initial Text'));
+
+ controller.text = 'Updated Text';
+ await tester.idle();
+
+ expect(tester.testTextInput.editingState['text'], equals('Updated Text'));
+
+ setState(() {
+ currentController = controller2;
+ });
+
+ await tester.pump();
+
+ expect(tester.testTextInput.editingState['text'], equals('More Text'));
+
+ controller.text = 'Ignored Text';
+ await tester.idle();
+
+ expect(tester.testTextInput.editingState['text'], equals('More Text'));
+
+ controller2.text = 'Final Text';
+ await tester.idle();
+
+ expect(tester.testTextInput.editingState['text'], equals('Final Text'));
+ });
}