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'));
+  });
 }