Skip to content

Commit e98e0b4

Browse files
authored
EditableText action handlers swallow errors (flutter#66851)
Errors that happen in user-defined callbacks (like onChanged) will now make it to the console.
1 parent 571b190 commit e98e0b4

File tree

2 files changed

+165
-7
lines changed

2 files changed

+165
-7
lines changed

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

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1806,7 +1806,16 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
18061806
void _finalizeEditing(TextInputAction action, {required bool shouldUnfocus}) {
18071807
// Take any actions necessary now that the user has completed editing.
18081808
if (widget.onEditingComplete != null) {
1809-
widget.onEditingComplete!();
1809+
try {
1810+
widget.onEditingComplete!();
1811+
} catch (exception, stack) {
1812+
FlutterError.reportError(FlutterErrorDetails(
1813+
exception: exception,
1814+
stack: stack,
1815+
library: 'widgets',
1816+
context: ErrorDescription('while calling onEditingComplete for $action'),
1817+
));
1818+
}
18101819
} else {
18111820
// Default behavior if the developer did not provide an
18121821
// onEditingComplete callback: Finalize editing and remove focus, or move
@@ -1838,8 +1847,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
18381847
}
18391848

18401849
// Invoke optional callback with the user's submitted content.
1841-
if (widget.onSubmitted != null)
1842-
widget.onSubmitted!(_value.text);
1850+
if (widget.onSubmitted != null) {
1851+
try {
1852+
widget.onSubmitted!(_value.text);
1853+
} catch (exception, stack) {
1854+
FlutterError.reportError(FlutterErrorDetails(
1855+
exception: exception,
1856+
stack: stack,
1857+
library: 'widgets',
1858+
context: ErrorDescription('while calling onSubmitted for $action'),
1859+
));
1860+
}
1861+
}
18431862
}
18441863

18451864
void _updateRemoteEditingValueIfNeeded() {
@@ -2053,8 +2072,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
20532072
);
20542073
_selectionOverlay!.handlesVisible = widget.showSelectionHandles;
20552074
_selectionOverlay!.showHandles();
2056-
if (widget.onSelectionChanged != null)
2057-
widget.onSelectionChanged!(selection, cause);
2075+
if (widget.onSelectionChanged != null) {
2076+
try {
2077+
widget.onSelectionChanged!(selection, cause);
2078+
} catch (exception, stack) {
2079+
FlutterError.reportError(FlutterErrorDetails(
2080+
exception: exception,
2081+
stack: stack,
2082+
library: 'widgets',
2083+
context: ErrorDescription('while calling onSelectionChanged for $cause'),
2084+
));
2085+
}
2086+
}
20582087
}
20592088
}
20602089

@@ -2178,8 +2207,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
21782207
_value = _lastFormattedValue!;
21792208
}
21802209

2181-
if (textChanged && widget.onChanged != null)
2182-
widget.onChanged!(value.text);
2210+
if (textChanged && widget.onChanged != null) {
2211+
try {
2212+
widget.onChanged!(value.text);
2213+
} catch (exception, stack) {
2214+
FlutterError.reportError(FlutterErrorDetails(
2215+
exception: exception,
2216+
stack: stack,
2217+
library: 'widgets',
2218+
context: ErrorDescription('while calling onChanged'),
2219+
));
2220+
}
2221+
}
21832222
_lastFormattedUnmodifiedTextEditingValue = _receivedRemoteTextEditingValue;
21842223
}
21852224

packages/flutter/test/widgets/editable_text_test.dart

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5776,6 +5776,125 @@ void main() {
57765776
expect(state.currentTextEditingValue.text, '12345');
57775777
expect(state.currentTextEditingValue.composing, TextRange.empty);
57785778
});
5779+
5780+
group('callback errors', () {
5781+
const String errorText = 'Test EditableText callback error';
5782+
5783+
testWidgets('onSelectionChanged can throw errors', (WidgetTester tester) async {
5784+
await tester.pumpWidget(MaterialApp(
5785+
home: EditableText(
5786+
showSelectionHandles: true,
5787+
maxLines: 2,
5788+
controller: TextEditingController(
5789+
text: 'flutter is the best!',
5790+
),
5791+
focusNode: FocusNode(),
5792+
cursorColor: Colors.red,
5793+
backgroundCursorColor: Colors.blue,
5794+
style: Typography.material2018(platform: TargetPlatform.android).black.subtitle1.copyWith(fontFamily: 'Roboto'),
5795+
keyboardType: TextInputType.text,
5796+
selectionControls: materialTextSelectionControls,
5797+
onSelectionChanged: (TextSelection selection, SelectionChangedCause cause) {
5798+
throw FlutterError(errorText);
5799+
},
5800+
),
5801+
));
5802+
5803+
// Interact with the field to establish the input connection.
5804+
await tester.tap(find.byType(EditableText));
5805+
final dynamic error = tester.takeException();
5806+
expect(error, isFlutterError);
5807+
expect(error.toString(), contains(errorText));
5808+
});
5809+
5810+
testWidgets('onChanged can throw errors', (WidgetTester tester) async {
5811+
await tester.pumpWidget(MaterialApp(
5812+
home: EditableText(
5813+
showSelectionHandles: true,
5814+
maxLines: 2,
5815+
controller: TextEditingController(
5816+
text: 'flutter is the best!',
5817+
),
5818+
focusNode: FocusNode(),
5819+
cursorColor: Colors.red,
5820+
backgroundCursorColor: Colors.blue,
5821+
style: Typography.material2018(platform: TargetPlatform.android).black.subtitle1.copyWith(fontFamily: 'Roboto'),
5822+
keyboardType: TextInputType.text,
5823+
onChanged: (String text) {
5824+
throw FlutterError(errorText);
5825+
},
5826+
),
5827+
));
5828+
5829+
// Modify the text and expect an error from onChanged.
5830+
await tester.enterText(find.byType(EditableText), '...');
5831+
final dynamic error = tester.takeException();
5832+
expect(error, isFlutterError);
5833+
expect(error.toString(), contains(errorText));
5834+
});
5835+
5836+
testWidgets('onEditingComplete can throw errors', (WidgetTester tester) async {
5837+
await tester.pumpWidget(MaterialApp(
5838+
home: EditableText(
5839+
showSelectionHandles: true,
5840+
maxLines: 2,
5841+
controller: TextEditingController(
5842+
text: 'flutter is the best!',
5843+
),
5844+
focusNode: FocusNode(),
5845+
cursorColor: Colors.red,
5846+
backgroundCursorColor: Colors.blue,
5847+
style: Typography.material2018(platform: TargetPlatform.android).black.subtitle1.copyWith(fontFamily: 'Roboto'),
5848+
keyboardType: TextInputType.text,
5849+
onEditingComplete: () {
5850+
throw FlutterError(errorText);
5851+
},
5852+
),
5853+
));
5854+
5855+
// Interact with the field to establish the input connection.
5856+
final Offset topLeft = tester.getTopLeft(find.byType(EditableText));
5857+
await tester.tapAt(topLeft + const Offset(0.0, 5.0));
5858+
await tester.pump();
5859+
5860+
// Submit and expect an error from onEditingComplete.
5861+
await tester.testTextInput.receiveAction(TextInputAction.done);
5862+
final dynamic error = tester.takeException();
5863+
expect(error, isFlutterError);
5864+
expect(error.toString(), contains(errorText));
5865+
});
5866+
5867+
testWidgets('onSubmitted can throw errors', (WidgetTester tester) async {
5868+
await tester.pumpWidget(MaterialApp(
5869+
home: EditableText(
5870+
showSelectionHandles: true,
5871+
maxLines: 2,
5872+
controller: TextEditingController(
5873+
text: 'flutter is the best!',
5874+
),
5875+
focusNode: FocusNode(),
5876+
cursorColor: Colors.red,
5877+
backgroundCursorColor: Colors.blue,
5878+
style: Typography.material2018(platform: TargetPlatform.android).black.subtitle1.copyWith(fontFamily: 'Roboto'),
5879+
keyboardType: TextInputType.text,
5880+
onSubmitted: (String text) {
5881+
throw FlutterError(errorText);
5882+
},
5883+
),
5884+
));
5885+
5886+
// Interact with the field to establish the input connection.
5887+
final Offset topLeft = tester.getTopLeft(find.byType(EditableText));
5888+
await tester.tapAt(topLeft + const Offset(0.0, 5.0));
5889+
await tester.pump();
5890+
5891+
// Submit and expect an error from onSubmitted.
5892+
await tester.testTextInput.receiveAction(TextInputAction.done);
5893+
final dynamic error = tester.takeException();
5894+
expect(error, isFlutterError);
5895+
expect(error.toString(), contains(errorText));
5896+
});
5897+
});
57795898
}
57805899

57815900
class MockTextFormatter extends TextInputFormatter {

0 commit comments

Comments
 (0)