Hi @mythz
Eventually this is a misunderstanding. With my own error handling I meant that I just wrote DART code to deal with the Future results I get when calling any async
(REST) API call and show a suitable message to the end user. On the server side I follow this recommendation for all my APIs.
So I create my own client models out of the ServiceStack generate DTOs, but only for the data inside the ResponseDTOs, not for all response DTO objects. Let me show you an example of one endpoint to see what is going on.
On the Server I defined the following DTOs to get a single instance of a CreditCard object (just a sample here)
[DataContract]
public class BscGetCreditCard : IBscRequestId, IReturn<BscGetCreditCardResponse>
{
[ApiMember(Name = "UniqueRequestId", Description = "A unique, client side generated request ID. Best bet is a GUID converted to a string.",
ParameterType = "path", DataType = "RequestMetaDataDto", IsRequired = true)]
[DataMember(Order = 1)]
public string UniqueRequestId { get; set; }
[ApiMember(Name = "CreditCardId", Description = "ID of the credit card.",
ParameterType = "path", DataType = "object", IsRequired = true)]
[DataMember(Order = 2)]
public string CreditCardId { get; set; }
[ApiMember(Name = "Fields", Description = "List of strings containing the names of credit card properties which should be returned. The `_id` " +
"field is ALWAYS returned. Field names are CASE SENSITIVE. Field names which cannot be found are " +
"silently ignored.",
ParameterType = "path", DataType = "Array", IsRequired = false)]
[DataMember(Order = 3)]
public List<string> Fields { get; set; }
}
[DataContract]
public class BscGetCreditCardResponse
{
[DataMember(Order = 1)]
public BscCreditCard CreditCard { get; set; }
[ApiMember(Name = "CreditCardProperties", Description = "List of key value pairs when `fields` where set in the request. NULL if `Fields` " +
"was NULL in the request. Properties which are not found are not returned in " +
"the Array.",
ParameterType = "path", DataType = "Array", IsRequired = false)]
[DataMember(Order = 2)]
public Dictionary<string, object> CreditCardProperties { get; set; }
[ApiMember(Name = "ResponseStatus", Description = "Contains exceptions and errors which may have occured during execution of the request. " +
"May contain additional information such as warnings in the `Meta` dictionary. NULL if " +
"no errors or other status messages where added.",
ParameterType = "path", DataType = "object", IsRequired = false)]
[DataMember(Order = 3)]
public ResponseStatus ResponseStatus { get; set; }
}
In my APIs I often have an optional fields parameter which allows to filter those property of an object that you need (instead of returning a large complete object). As you can see, the Response
object has three members:
-
CreditCard
DTO which contains the entire object. This is filled if you did not fill the Fields
array with property names of the object. If you did, the CreditCard
DTO will be null
. It can also be null
, if a CreditCardId
was submitted that could not be found on the server (rarely the case but possible)
-
Dictionary<string, object>
if you submitted the Fields
array. In that case the CreditCard
DTO will be null
and the dictionary returns those properties you requested in a Dictionary
(which is serialized by your code to a Map
for DART. The dictionary can be null
if you submitted unknown property names or if nothing was found (same as above)
-
ResponseStatus
which gets all exceptions and errors injected. This is null
if no errors occured.
This means I have to test all three possibilities in the list above on the client. The first check is always to test if ResponseStatus
is null
. If it is not, it means there is an error and usually no data is in the Response
object. I then display the errors to the user. If the Response
object contains data, I convert this data to my own client models.
So in Dart I usually get a Future<ResponseDTO>
back when calling a ServiceStack service. To check the result I have code similar to the following:
Future<bool> _fetchCreditCard() async {
print('Fetching credit card data...');
try {
var response = await locator<CreditCardService>().getCreditCard(widget.ccId);
if (response.responseStatus != null) {
print('There was a server error...');
//server error
String message = response.responseStatus?.message as String;
SnackBar sb = SnackbarBuilder(
context: context, message: message, durationSeconds: 10, messageType: Enums.infoMessageType.Error)
.buildSnackBar();
ScaffoldMessenger.of(context).showSnackBar(sb);
} else if (response.creditCard != null) {
print('Got data from server....');
// success
serverCc = CreditCard.fromDto(response.creditCard!);
updatedCc = CreditCard.fromCreditCard(serverCc);
return true;
} else {
String message = 'The returned response did not contain any data, nor did it contain errors.';
SnackBar sb = SnackbarBuilder(
context: context, message: message, durationSeconds: 5, messageType: Enums.infoMessageType.Warning)
.buildSnackBar();
ScaffoldMessenger.of(context).showSnackBar(sb);
}
return false;
} catch (error) {
// throw Exception(error);
print(error.toString());
return false;
}
}
(This method returns a Future<bool>
because of Flutter state management, it is called in the initState
method of a UI Widget and uses a Builder
to show the result or an error message. I don`t elaborate more on this, there are people who have written books about state management…)
But back to what I meant with my own error handling
. In the _fetchCreditCard()
method you see the checks I mentioned above. In the first if statement
I check if response.responseStatus != null
which would mean there is an error which I will show in a SnackBar
control. Since I called the Request without submitting the fields
array, I do not check the Dictionary
at all.
So everything works fine if a CreditCard
DTO is returned. To test if error messages are correctly displayed, I nulled the returned CreditCard
DTO on the server side service and filled the ResponseStatus
object of the ResponseDTO
manually manually with some fake data.
I saw this working already but I think last week we had updates on Flutter. I will test in other places. But currently it is a matter of fact, that whatever I get back from the .NET Server, the ResponseStatus
object is always null which means my error handling in Dart is not working anymore.
UPDATE
Something is going terribly wrong here! I now throw an Exception on the Server instead of returning something. Now this is appears in the catch block of my Dart code, NOT in the response object. The error is of type object
but in the debugger I can see that it contains a ResponseStatus
:
So this seems to be a problem when using try ... catch
in dart and not a FutureBuilder
. I need to research this further…