mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
EditableText action handlers swallow errors (#66851)
Errors that happen in user-defined callbacks (like onChanged) will now make it to the console.
This commit is contained in:
parent
571b190f07
commit
e98e0b409f
|
@ -1806,7 +1806,16 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||
void _finalizeEditing(TextInputAction action, {required bool shouldUnfocus}) {
|
||||
// Take any actions necessary now that the user has completed editing.
|
||||
if (widget.onEditingComplete != null) {
|
||||
widget.onEditingComplete!();
|
||||
try {
|
||||
widget.onEditingComplete!();
|
||||
} catch (exception, stack) {
|
||||
FlutterError.reportError(FlutterErrorDetails(
|
||||
exception: exception,
|
||||
stack: stack,
|
||||
library: 'widgets',
|
||||
context: ErrorDescription('while calling onEditingComplete for $action'),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// Default behavior if the developer did not provide an
|
||||
// onEditingComplete callback: Finalize editing and remove focus, or move
|
||||
|
@ -1838,8 +1847,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||
}
|
||||
|
||||
// Invoke optional callback with the user's submitted content.
|
||||
if (widget.onSubmitted != null)
|
||||
widget.onSubmitted!(_value.text);
|
||||
if (widget.onSubmitted != null) {
|
||||
try {
|
||||
widget.onSubmitted!(_value.text);
|
||||
} catch (exception, stack) {
|
||||
FlutterError.reportError(FlutterErrorDetails(
|
||||
exception: exception,
|
||||
stack: stack,
|
||||
library: 'widgets',
|
||||
context: ErrorDescription('while calling onSubmitted for $action'),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _updateRemoteEditingValueIfNeeded() {
|
||||
|
@ -2053,8 +2072,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||
);
|
||||
_selectionOverlay!.handlesVisible = widget.showSelectionHandles;
|
||||
_selectionOverlay!.showHandles();
|
||||
if (widget.onSelectionChanged != null)
|
||||
widget.onSelectionChanged!(selection, cause);
|
||||
if (widget.onSelectionChanged != null) {
|
||||
try {
|
||||
widget.onSelectionChanged!(selection, cause);
|
||||
} catch (exception, stack) {
|
||||
FlutterError.reportError(FlutterErrorDetails(
|
||||
exception: exception,
|
||||
stack: stack,
|
||||
library: 'widgets',
|
||||
context: ErrorDescription('while calling onSelectionChanged for $cause'),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2178,8 +2207,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||
_value = _lastFormattedValue!;
|
||||
}
|
||||
|
||||
if (textChanged && widget.onChanged != null)
|
||||
widget.onChanged!(value.text);
|
||||
if (textChanged && widget.onChanged != null) {
|
||||
try {
|
||||
widget.onChanged!(value.text);
|
||||
} catch (exception, stack) {
|
||||
FlutterError.reportError(FlutterErrorDetails(
|
||||
exception: exception,
|
||||
stack: stack,
|
||||
library: 'widgets',
|
||||
context: ErrorDescription('while calling onChanged'),
|
||||
));
|
||||
}
|
||||
}
|
||||
_lastFormattedUnmodifiedTextEditingValue = _receivedRemoteTextEditingValue;
|
||||
}
|
||||
|
||||
|
|
|
@ -5776,6 +5776,125 @@ void main() {
|
|||
expect(state.currentTextEditingValue.text, '12345');
|
||||
expect(state.currentTextEditingValue.composing, TextRange.empty);
|
||||
});
|
||||
|
||||
group('callback errors', () {
|
||||
const String errorText = 'Test EditableText callback error';
|
||||
|
||||
testWidgets('onSelectionChanged can throw errors', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: EditableText(
|
||||
showSelectionHandles: true,
|
||||
maxLines: 2,
|
||||
controller: TextEditingController(
|
||||
text: 'flutter is the best!',
|
||||
),
|
||||
focusNode: FocusNode(),
|
||||
cursorColor: Colors.red,
|
||||
backgroundCursorColor: Colors.blue,
|
||||
style: Typography.material2018(platform: TargetPlatform.android).black.subtitle1.copyWith(fontFamily: 'Roboto'),
|
||||
keyboardType: TextInputType.text,
|
||||
selectionControls: materialTextSelectionControls,
|
||||
onSelectionChanged: (TextSelection selection, SelectionChangedCause cause) {
|
||||
throw FlutterError(errorText);
|
||||
},
|
||||
),
|
||||
));
|
||||
|
||||
// Interact with the field to establish the input connection.
|
||||
await tester.tap(find.byType(EditableText));
|
||||
final dynamic error = tester.takeException();
|
||||
expect(error, isFlutterError);
|
||||
expect(error.toString(), contains(errorText));
|
||||
});
|
||||
|
||||
testWidgets('onChanged can throw errors', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: EditableText(
|
||||
showSelectionHandles: true,
|
||||
maxLines: 2,
|
||||
controller: TextEditingController(
|
||||
text: 'flutter is the best!',
|
||||
),
|
||||
focusNode: FocusNode(),
|
||||
cursorColor: Colors.red,
|
||||
backgroundCursorColor: Colors.blue,
|
||||
style: Typography.material2018(platform: TargetPlatform.android).black.subtitle1.copyWith(fontFamily: 'Roboto'),
|
||||
keyboardType: TextInputType.text,
|
||||
onChanged: (String text) {
|
||||
throw FlutterError(errorText);
|
||||
},
|
||||
),
|
||||
));
|
||||
|
||||
// Modify the text and expect an error from onChanged.
|
||||
await tester.enterText(find.byType(EditableText), '...');
|
||||
final dynamic error = tester.takeException();
|
||||
expect(error, isFlutterError);
|
||||
expect(error.toString(), contains(errorText));
|
||||
});
|
||||
|
||||
testWidgets('onEditingComplete can throw errors', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: EditableText(
|
||||
showSelectionHandles: true,
|
||||
maxLines: 2,
|
||||
controller: TextEditingController(
|
||||
text: 'flutter is the best!',
|
||||
),
|
||||
focusNode: FocusNode(),
|
||||
cursorColor: Colors.red,
|
||||
backgroundCursorColor: Colors.blue,
|
||||
style: Typography.material2018(platform: TargetPlatform.android).black.subtitle1.copyWith(fontFamily: 'Roboto'),
|
||||
keyboardType: TextInputType.text,
|
||||
onEditingComplete: () {
|
||||
throw FlutterError(errorText);
|
||||
},
|
||||
),
|
||||
));
|
||||
|
||||
// Interact with the field to establish the input connection.
|
||||
final Offset topLeft = tester.getTopLeft(find.byType(EditableText));
|
||||
await tester.tapAt(topLeft + const Offset(0.0, 5.0));
|
||||
await tester.pump();
|
||||
|
||||
// Submit and expect an error from onEditingComplete.
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
final dynamic error = tester.takeException();
|
||||
expect(error, isFlutterError);
|
||||
expect(error.toString(), contains(errorText));
|
||||
});
|
||||
|
||||
testWidgets('onSubmitted can throw errors', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: EditableText(
|
||||
showSelectionHandles: true,
|
||||
maxLines: 2,
|
||||
controller: TextEditingController(
|
||||
text: 'flutter is the best!',
|
||||
),
|
||||
focusNode: FocusNode(),
|
||||
cursorColor: Colors.red,
|
||||
backgroundCursorColor: Colors.blue,
|
||||
style: Typography.material2018(platform: TargetPlatform.android).black.subtitle1.copyWith(fontFamily: 'Roboto'),
|
||||
keyboardType: TextInputType.text,
|
||||
onSubmitted: (String text) {
|
||||
throw FlutterError(errorText);
|
||||
},
|
||||
),
|
||||
));
|
||||
|
||||
// Interact with the field to establish the input connection.
|
||||
final Offset topLeft = tester.getTopLeft(find.byType(EditableText));
|
||||
await tester.tapAt(topLeft + const Offset(0.0, 5.0));
|
||||
await tester.pump();
|
||||
|
||||
// Submit and expect an error from onSubmitted.
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
final dynamic error = tester.takeException();
|
||||
expect(error, isFlutterError);
|
||||
expect(error.toString(), contains(errorText));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MockTextFormatter extends TextInputFormatter {
|
||||
|
|
Loading…
Reference in a new issue