diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index 5ffec58c08..f8b4c82b4a 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -239,9 +239,8 @@ class EditableTextState extends State implements TextInputClient { 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 @@ class EditableTextState extends State implements TextInputClient { // 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 @@ class EditableTextState extends State implements TextInputClient { 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 @@ class EditableTextState extends State implements TextInputClient { 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 @@ class EditableTextState extends State implements TextInputClient { if (_hasInputConnection) { _textInputConnection.close(); _textInputConnection = null; + _lastKnownRemoteTextEditingValue = null; } } @@ -429,6 +444,7 @@ class EditableTextState extends State implements TextInputClient { } void _didChangeTextEditingValue() { + _updateRemoteEditingValueIfNeeded(); _startOrStopCursorTimerIfNeeded(); _updateOrDisposeSelectionOverlayIfNeeded(); // TODO(abarth): Teach RenderEditable about ValueNotifier diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 7609d12daa..4cf3486b69 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -886,4 +886,59 @@ void main() { 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')); + }); }