Unverified Commit 72707416 authored by LongCatIsLooong's avatar LongCatIsLooong Committed by GitHub

Prevent committing text from triggering EditableText.onChanged (#112010)

parent c34e9071
......@@ -2924,19 +2924,20 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@pragma('vm:notify-debugger-on-exception')
void _formatAndSetValue(TextEditingValue value, SelectionChangedCause? cause, {bool userInteraction = false}) {
// Only apply input formatters if the text has changed (including uncommitted
// text in the composing region), or when the user committed the composing
// text.
// Gboard is very persistent in restoring the composing region. Applying
// input formatters on composing-region-only changes (except clearing the
// current composing region) is very infinite-loop-prone: the formatters
// will keep trying to modify the composing region while Gboard will keep
// trying to restore the original composing region.
final bool textChanged = _value.text != value.text
|| (!_value.composing.isCollapsed && value.composing.isCollapsed);
final bool selectionChanged = _value.selection != value.selection;
if (textChanged) {
final TextEditingValue oldValue = _value;
final bool textChanged = oldValue.text != value.text;
final bool textCommitted = !oldValue.composing.isCollapsed && value.composing.isCollapsed;
final bool selectionChanged = oldValue.selection != value.selection;
if (textChanged || textCommitted) {
// Only apply input formatters if the text has changed (including uncommitted
// text in the composing region), or when the user committed the composing
// text.
// Gboard is very persistent in restoring the composing region. Applying
// input formatters on composing-region-only changes (except clearing the
// current composing region) is very infinite-loop-prone: the formatters
// will keep trying to modify the composing region while Gboard will keep
// trying to restore the original composing region.
try {
value = widget.inputFormatters?.fold<TextEditingValue>(
value,
......@@ -2970,9 +2971,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
cause == SelectionChangedCause.keyboard))) {
_handleSelectionChanged(_value.selection, cause);
}
if (textChanged) {
final String currentText = _value.text;
if (oldValue.text != currentText) {
try {
widget.onChanged?.call(_value.text);
widget.onChanged?.call(currentText);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
......@@ -2982,7 +2984,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
));
}
}
endBatchEdit();
}
......
......@@ -4312,6 +4312,52 @@ void main() {
expect(render.text!.style!.fontStyle, FontStyle.italic);
});
testWidgets('onChanged callback only invoked on text changes', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/111651 .
final TextEditingController controller = TextEditingController();
int onChangedCount = 0;
bool preventInput = false;
final TextInputFormatter formatter = TextInputFormatter.withFunction((TextEditingValue oldValue, TextEditingValue newValue) {
return preventInput ? oldValue : newValue;
});
final Widget widget = MediaQuery(
data: const MediaQueryData(),
child: EditableText(
controller: controller,
backgroundCursorColor: Colors.red,
cursorColor: Colors.red,
focusNode: FocusNode(),
style: textStyle,
onChanged: (String newString) { onChangedCount += 1; },
inputFormatters: <TextInputFormatter>[formatter],
textDirection: TextDirection.ltr,
),
);
await tester.pumpWidget(widget);
final EditableTextState state = tester.firstState(find.byType(EditableText));
state.updateEditingValue(
const TextEditingValue(text: 'a', composing: TextRange(start: 0, end: 1)),
);
expect(onChangedCount , 1);
state.updateEditingValue(
const TextEditingValue(text: 'a'),
);
expect(onChangedCount , 1);
state.updateEditingValue(
const TextEditingValue(text: 'ab'),
);
expect(onChangedCount , 2);
preventInput = true;
state.updateEditingValue(
const TextEditingValue(text: 'abc'),
);
expect(onChangedCount , 2);
});
testWidgets('Formatters are skipped if text has not changed', (WidgetTester tester) async {
int called = 0;
final TextInputFormatter formatter = TextInputFormatter.withFunction((TextEditingValue oldValue, TextEditingValue newValue) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment