Fix TextField bug when the formatter repeatedly format (#67892)
diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart
index 5bb9612..97a8dd1 100644
--- a/packages/flutter/lib/src/widgets/editable_text.dart
+++ b/packages/flutter/lib/src/widgets/editable_text.dart
@@ -2212,8 +2212,15 @@
_lastFormattedValue = value;
}
- // Setting _value here ensures the selection and composing region info is passed.
- _value = value;
+ if (value == _value) {
+ // If the value was modified by the formatter, the remote should be notified to keep in sync,
+ // if not modified, it will short-circuit.
+ _updateRemoteEditingValueIfNeeded();
+ } else {
+ // Setting _value here ensures the selection and composing region info is passed.
+ _value = value;
+ }
+
// Use the last formatted value when an identical repeat pass is detected.
if (isRepeat && textChanged && _lastFormattedValue != null) {
_value = _lastFormattedValue!;
diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart
index 2872d7a..01f8942 100644
--- a/packages/flutter/test/widgets/editable_text_test.dart
+++ b/packages/flutter/test/widgets/editable_text_test.dart
@@ -4998,6 +4998,84 @@
);
});
+ testWidgets('Send text input state to engine when the input formatter rejects user input', (WidgetTester tester) async {
+ // Regression test for https://github.com/flutter/flutter/issues/67828
+ final List<MethodCall> log = <MethodCall>[];
+ SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async {
+ log.add(methodCall);
+ });
+ final TextInputFormatter formatter = TextInputFormatter.withFunction((TextEditingValue oldValue, TextEditingValue newValue) {
+ return const TextEditingValue(text: 'Flutter is the best!');
+ });
+ final TextEditingController controller = TextEditingController();
+
+ final FocusNode focusNode = FocusNode(debugLabel: 'EditableText Focus Node');
+ Widget builder() {
+ return StatefulBuilder(
+ builder: (BuildContext context, StateSetter setter) {
+ return MaterialApp(
+ home: MediaQuery(
+ data: const MediaQueryData(devicePixelRatio: 1.0),
+ child: Directionality(
+ textDirection: TextDirection.ltr,
+ child: Center(
+ child: Material(
+ child: EditableText(
+ controller: controller,
+ focusNode: focusNode,
+ style: textStyle,
+ cursorColor: Colors.red,
+ backgroundCursorColor: Colors.red,
+ keyboardType: TextInputType.multiline,
+ inputFormatters: <TextInputFormatter>[
+ formatter,
+ ],
+ onChanged: (String value) { },
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ },
+ );
+ }
+
+ await tester.pumpWidget(builder());
+ await tester.tap(find.byType(EditableText));
+ await tester.showKeyboard(find.byType(EditableText));
+ await tester.pump();
+
+ log.clear();
+
+ final EditableTextState state = tester.firstState(find.byType(EditableText));
+
+ // setEditingState is called when remote value modified by the formatter.
+ state.updateEditingValue(const TextEditingValue(
+ text: 'I will be modified by the formatter.',
+ ));
+ expect(log.length, 1);
+ expect(log, contains(matchesMethodCall(
+ 'TextInput.setEditingState',
+ args: allOf(
+ containsPair('text', 'Flutter is the best!'),
+ ),
+ )));
+
+ log.clear();
+
+ state.updateEditingValue(const TextEditingValue(
+ text: 'I will be modified by the formatter.',
+ ));
+ expect(log.length, 1);
+ expect(log, contains(matchesMethodCall(
+ 'TextInput.setEditingState',
+ args: allOf(
+ containsPair('text', 'Flutter is the best!'),
+ ),
+ )));
+ });
+
testWidgets('autofocus:true on first frame does not throw', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(text: testText);
controller.selection = const TextSelection(