Skip to content

Commit 8538b4f

Browse files
authored
Fix TextField bug when the formatter repeatedly format (#67892)
1 parent 345188d commit 8538b4f

File tree

2 files changed

+87
-2
lines changed

2 files changed

+87
-2
lines changed

packages/flutter/lib/src/widgets/editable_text.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2212,8 +2212,15 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
22122212
_lastFormattedValue = value;
22132213
}
22142214

2215-
// Setting _value here ensures the selection and composing region info is passed.
2216-
_value = value;
2215+
if (value == _value) {
2216+
// If the value was modified by the formatter, the remote should be notified to keep in sync,
2217+
// if not modified, it will short-circuit.
2218+
_updateRemoteEditingValueIfNeeded();
2219+
} else {
2220+
// Setting _value here ensures the selection and composing region info is passed.
2221+
_value = value;
2222+
}
2223+
22172224
// Use the last formatted value when an identical repeat pass is detected.
22182225
if (isRepeat && textChanged && _lastFormattedValue != null) {
22192226
_value = _lastFormattedValue!;

packages/flutter/test/widgets/editable_text_test.dart

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4998,6 +4998,84 @@ void main() {
49984998
);
49994999
});
50005000

5001+
testWidgets('Send text input state to engine when the input formatter rejects user input', (WidgetTester tester) async {
5002+
// Regression test for https://github.com/flutter/flutter/issues/67828
5003+
final List<MethodCall> log = <MethodCall>[];
5004+
SystemChannels.textInput.setMockMethodCallHandler((MethodCall methodCall) async {
5005+
log.add(methodCall);
5006+
});
5007+
final TextInputFormatter formatter = TextInputFormatter.withFunction((TextEditingValue oldValue, TextEditingValue newValue) {
5008+
return const TextEditingValue(text: 'Flutter is the best!');
5009+
});
5010+
final TextEditingController controller = TextEditingController();
5011+
5012+
final FocusNode focusNode = FocusNode(debugLabel: 'EditableText Focus Node');
5013+
Widget builder() {
5014+
return StatefulBuilder(
5015+
builder: (BuildContext context, StateSetter setter) {
5016+
return MaterialApp(
5017+
home: MediaQuery(
5018+
data: const MediaQueryData(devicePixelRatio: 1.0),
5019+
child: Directionality(
5020+
textDirection: TextDirection.ltr,
5021+
child: Center(
5022+
child: Material(
5023+
child: EditableText(
5024+
controller: controller,
5025+
focusNode: focusNode,
5026+
style: textStyle,
5027+
cursorColor: Colors.red,
5028+
backgroundCursorColor: Colors.red,
5029+
keyboardType: TextInputType.multiline,
5030+
inputFormatters: <TextInputFormatter>[
5031+
formatter,
5032+
],
5033+
onChanged: (String value) { },
5034+
),
5035+
),
5036+
),
5037+
),
5038+
),
5039+
);
5040+
},
5041+
);
5042+
}
5043+
5044+
await tester.pumpWidget(builder());
5045+
await tester.tap(find.byType(EditableText));
5046+
await tester.showKeyboard(find.byType(EditableText));
5047+
await tester.pump();
5048+
5049+
log.clear();
5050+
5051+
final EditableTextState state = tester.firstState(find.byType(EditableText));
5052+
5053+
// setEditingState is called when remote value modified by the formatter.
5054+
state.updateEditingValue(const TextEditingValue(
5055+
text: 'I will be modified by the formatter.',
5056+
));
5057+
expect(log.length, 1);
5058+
expect(log, contains(matchesMethodCall(
5059+
'TextInput.setEditingState',
5060+
args: allOf(
5061+
containsPair('text', 'Flutter is the best!'),
5062+
),
5063+
)));
5064+
5065+
log.clear();
5066+
5067+
state.updateEditingValue(const TextEditingValue(
5068+
text: 'I will be modified by the formatter.',
5069+
));
5070+
expect(log.length, 1);
5071+
expect(log, contains(matchesMethodCall(
5072+
'TextInput.setEditingState',
5073+
args: allOf(
5074+
containsPair('text', 'Flutter is the best!'),
5075+
),
5076+
)));
5077+
});
5078+
50015079
testWidgets('autofocus:true on first frame does not throw', (WidgetTester tester) async {
50025080
final TextEditingController controller = TextEditingController(text: testText);
50035081
controller.selection = const TextSelection(

0 commit comments

Comments
 (0)