-
Notifications
You must be signed in to change notification settings - Fork 338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Property Editor] Handle errors from the Analysis Server #8818
Changes from 2 commits
93737c2
f58c474
c1394bf
57089dd
ff72e62
8bb823a
bf86128
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -6,6 +6,7 @@ import 'package:devtools_app_shared/ui.dart'; | |||||||||||||||||||
import 'package:flutter/material.dart'; | ||||||||||||||||||||
import 'package:flutter/services.dart'; | ||||||||||||||||||||
|
||||||||||||||||||||
import '../../../shared/editor/api_classes.dart'; | ||||||||||||||||||||
import 'property_editor_controller.dart'; | ||||||||||||||||||||
import 'property_editor_types.dart'; | ||||||||||||||||||||
|
||||||||||||||||||||
|
@@ -89,48 +90,69 @@ class StringInput extends StatelessWidget { | |||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
class _DropdownInput<T> extends StatelessWidget with _PropertyInputMixin<T> { | ||||||||||||||||||||
_DropdownInput({super.key, required this.property, required this.controller}); | ||||||||||||||||||||
class _DropdownInput<T> extends StatefulWidget { | ||||||||||||||||||||
const _DropdownInput({ | ||||||||||||||||||||
super.key, | ||||||||||||||||||||
required this.property, | ||||||||||||||||||||
required this.controller, | ||||||||||||||||||||
}); | ||||||||||||||||||||
|
||||||||||||||||||||
final FiniteValuesProperty property; | ||||||||||||||||||||
final PropertyEditorController controller; | ||||||||||||||||||||
|
||||||||||||||||||||
@override | ||||||||||||||||||||
State<_DropdownInput<T>> createState() => _DropdownInputState<T>(); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
class _DropdownInputState<T> extends State<_DropdownInput<T>> | ||||||||||||||||||||
with _PropertyInputMixin<_DropdownInput<T>, T> { | ||||||||||||||||||||
@override | ||||||||||||||||||||
Widget build(BuildContext context) { | ||||||||||||||||||||
final theme = Theme.of(context); | ||||||||||||||||||||
return DropdownButtonFormField( | ||||||||||||||||||||
value: property.valueDisplay, | ||||||||||||||||||||
decoration: decoration(property, theme: theme, padding: denseSpacing), | ||||||||||||||||||||
value: widget.property.valueDisplay, | ||||||||||||||||||||
autovalidateMode: AutovalidateMode.onUserInteraction, | ||||||||||||||||||||
validator: (text) => inputValidator(text, property: widget.property), | ||||||||||||||||||||
decoration: decoration( | ||||||||||||||||||||
widget.property, | ||||||||||||||||||||
theme: theme, | ||||||||||||||||||||
padding: denseSpacing, | ||||||||||||||||||||
), | ||||||||||||||||||||
isExpanded: true, | ||||||||||||||||||||
items: | ||||||||||||||||||||
property.propertyOptions.map((option) { | ||||||||||||||||||||
widget.property.propertyOptions.map((option) { | ||||||||||||||||||||
return DropdownMenuItem( | ||||||||||||||||||||
value: option, | ||||||||||||||||||||
child: Text(option, style: theme.fixedFontStyle), | ||||||||||||||||||||
); | ||||||||||||||||||||
}).toList(), | ||||||||||||||||||||
onChanged: (newValue) async { | ||||||||||||||||||||
await editProperty( | ||||||||||||||||||||
property, | ||||||||||||||||||||
widget.property, | ||||||||||||||||||||
valueAsString: newValue, | ||||||||||||||||||||
controller: controller, | ||||||||||||||||||||
controller: widget.controller, | ||||||||||||||||||||
); | ||||||||||||||||||||
}, | ||||||||||||||||||||
); | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
class _TextInput<T> extends StatefulWidget with _PropertyInputMixin<T> { | ||||||||||||||||||||
_TextInput({super.key, required this.property, required this.controller}); | ||||||||||||||||||||
class _TextInput<T> extends StatefulWidget { | ||||||||||||||||||||
const _TextInput({ | ||||||||||||||||||||
super.key, | ||||||||||||||||||||
required this.property, | ||||||||||||||||||||
required this.controller, | ||||||||||||||||||||
}); | ||||||||||||||||||||
|
||||||||||||||||||||
final EditableProperty property; | ||||||||||||||||||||
final PropertyEditorController controller; | ||||||||||||||||||||
|
||||||||||||||||||||
@override | ||||||||||||||||||||
State<_TextInput> createState() => _TextInputState(); | ||||||||||||||||||||
State<_TextInput> createState() => _TextInputState<T>(); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
class _TextInputState extends State<_TextInput> { | ||||||||||||||||||||
class _TextInputState<T> extends State<_TextInput<T>> | ||||||||||||||||||||
with _PropertyInputMixin<_TextInput<T>, T> { | ||||||||||||||||||||
String currentValue = ''; | ||||||||||||||||||||
|
||||||||||||||||||||
double paddingDiffComparedToDropdown = 1.0; | ||||||||||||||||||||
|
@@ -142,9 +164,9 @@ class _TextInputState extends State<_TextInput> { | |||||||||||||||||||
initialValue: widget.property.valueDisplay, | ||||||||||||||||||||
enabled: widget.property.isEditable, | ||||||||||||||||||||
autovalidateMode: AutovalidateMode.onUserInteraction, | ||||||||||||||||||||
validator: widget.property.inputValidator, | ||||||||||||||||||||
validator: (text) => inputValidator(text, property: widget.property), | ||||||||||||||||||||
inputFormatters: [FilteringTextInputFormatter.singleLineFormatter], | ||||||||||||||||||||
decoration: widget.decoration( | ||||||||||||||||||||
decoration: decoration( | ||||||||||||||||||||
widget.property, | ||||||||||||||||||||
theme: theme, | ||||||||||||||||||||
// Note: The text input has an extra pixel compared to the dropdown | ||||||||||||||||||||
|
@@ -154,6 +176,7 @@ class _TextInputState extends State<_TextInput> { | |||||||||||||||||||
), | ||||||||||||||||||||
style: theme.fixedFontStyle, | ||||||||||||||||||||
onChanged: (newValue) { | ||||||||||||||||||||
clearServerError(); | ||||||||||||||||||||
setState(() { | ||||||||||||||||||||
currentValue = newValue; | ||||||||||||||||||||
}); | ||||||||||||||||||||
|
@@ -166,20 +189,23 @@ class _TextInputState extends State<_TextInput> { | |||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
Future<void> _editProperty() async { | ||||||||||||||||||||
await widget.editProperty( | ||||||||||||||||||||
await editProperty( | ||||||||||||||||||||
widget.property, | ||||||||||||||||||||
valueAsString: currentValue, | ||||||||||||||||||||
controller: widget.controller, | ||||||||||||||||||||
); | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
mixin _PropertyInputMixin<T> { | ||||||||||||||||||||
mixin _PropertyInputMixin<T extends StatefulWidget, U> on State<T> { | ||||||||||||||||||||
String? _serverError; | ||||||||||||||||||||
|
||||||||||||||||||||
Future<void> editProperty( | ||||||||||||||||||||
EditableProperty property, { | ||||||||||||||||||||
required PropertyEditorController controller, | ||||||||||||||||||||
required String? valueAsString, | ||||||||||||||||||||
}) async { | ||||||||||||||||||||
clearServerError(); | ||||||||||||||||||||
final argName = property.name; | ||||||||||||||||||||
|
||||||||||||||||||||
// Can edit values to null. | ||||||||||||||||||||
|
@@ -188,8 +214,9 @@ mixin _PropertyInputMixin<T> { | |||||||||||||||||||
return; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
final value = property.convertFromInputString(valueAsString) as T?; | ||||||||||||||||||||
await controller.editArgument(name: argName, value: value); | ||||||||||||||||||||
final value = property.convertFromInputString(valueAsString) as U?; | ||||||||||||||||||||
final response = await controller.editArgument(name: argName, value: value); | ||||||||||||||||||||
_maybeHandleServerError(response, property: property); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
InputDecoration decoration( | ||||||||||||||||||||
|
@@ -231,4 +258,44 @@ mixin _PropertyInputMixin<T> { | |||||||||||||||||||
), | ||||||||||||||||||||
); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
String? inputValidator(String? input, {required EditableProperty property}) { | ||||||||||||||||||||
if (_serverError != null) return _serverError; | ||||||||||||||||||||
return property.inputValidator(input); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
void clearServerError() { | ||||||||||||||||||||
setState(() { | ||||||||||||||||||||
_serverError = null; | ||||||||||||||||||||
}); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
void _maybeHandleServerError( | ||||||||||||||||||||
EditArgumentResponse? errorResponse, { | ||||||||||||||||||||
required EditableProperty property, | ||||||||||||||||||||
}) { | ||||||||||||||||||||
if (errorResponse == null || errorResponse.success) return; | ||||||||||||||||||||
setState(() { | ||||||||||||||||||||
_serverError = _errorMessage(errorResponse.errorType, property: property); | ||||||||||||||||||||
}); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
String _errorMessage( | ||||||||||||||||||||
EditArgumentError? errorType, { | ||||||||||||||||||||
required EditableProperty property, | ||||||||||||||||||||
}) { | ||||||||||||||||||||
final propertyName = property.name; | ||||||||||||||||||||
switch (errorType) { | ||||||||||||||||||||
case EditArgumentError.editArgumentInvalidParameter: | ||||||||||||||||||||
return 'Invalid parameter: $propertyName.'; | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what do you think about including the description in the EditArgumentError definitions so that the messages are defined where the enum values are? Something like:
General question. Seems like we use Argument in some places and Property in others. Should we pick a single term for consistency? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, that's better! Made the change.
I've been trying to use Lines 70 to 78 in 80bbb54
But I don't have a strong preference for renaming everywhere vs keeping as is! |
||||||||||||||||||||
case EditArgumentError.editArgumentInvalidPosition: | ||||||||||||||||||||
return 'Invalid position for parameter: $propertyName.'; | ||||||||||||||||||||
case EditArgumentError.editArgumentInvalidValue: | ||||||||||||||||||||
return 'Invalid value for parameter: $propertyName.'; | ||||||||||||||||||||
case EditArgumentError.editsUnsupportedByEditor: | ||||||||||||||||||||
return 'IDE does not support property edits.'; | ||||||||||||||||||||
default: | ||||||||||||||||||||
return 'Encountered unknown error editing ${property.name}.'; | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,7 @@ dependencies: | |
http: ^1.1.0 | ||
image: ^4.1.3 | ||
intl: ^0.19.0 | ||
json_rpc_2: ^3.0.0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need to bring this dependency in? Is this for the RpcException type, and is this the type that DTD throws? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is for the |
||
logging: ^1.1.1 | ||
meta: ^1.9.1 | ||
mime: ^2.0.0 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we have a test that covers this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I was trying to think how we could test this. We can't pull in analysis_server as a dependency (otherwise I would just use the error definitions there instead) so I'm not sure how we could have a test to check they are in sync. Any ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we load the raw content of this file from GitHub and parse it? A bit hacky but still could be worth it to have a way to catch changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filed #8824 and added TODOs