Issue
I am sending API requests to a backend API using Spring in Android (Java). My question is how to receive validation errors to the error handler at ex 400 bad request response. Here is my code:
class RestTask extends AsyncTask<String,Void,ResponseEntity<ExpectedReturn>>
{
protected ResponseEntity<ExpectedReturn> doInBackground(String... uri)
{
try{
final String url = uri[0];
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(subscriber.getErrorHandler());
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
// set authentication tokens:
ResponseEntity<ExpectedReturn> response = restTemplate.exchange(url,callMethod,httpEntity, expectedReturnClass);
return response;
}catch(Exception e)
{
System.out.println(e.getMessage());
}
return null;
}
@Override
protected void onPostExecute(ResponseEntity<ExpectedReturn> result) {
if(result !=null && result.getBody() !=null)
{
subscriber.getSubscriber().onComplete(result.getBody(),result.getStatusCode());
}
}
}
My question is, if the post data fails validation (is incorrect), the API will return as JSON error object with errors, ex:
In case of a validation error, the error handler is called with a ClientHttpResponse object as a parameter. Calling the response.getBody() returns an InputStream. My question is, is there any way of receiving an object mapped from the JSON error response (as shown above) to the error handler, or perhaps converting the input stream to something readable (like a hashmap) so I can display the errors returned by the API (ex: "Name is required" etc...)?
Solution
I've tested your code and in case of a 400 bad request the catch block receives an instance of HttpClientErrorException
which has a method to get the error body as String:
private class HttpRequestTask extends AsyncTask<Void, Void, String> {
@Override
protected String doInBackground(Void... params) {
try {
final String url = "https://reqres.in/api/login";
RestTemplate restTemplate = new RestTemplate();
//Same result with restTemplate.exchange() too
return restTemplate.postForObject(url, "{\n" +
" \"email\": \"peter@klaven\"\n" +
"}", String.class);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
if (e instanceof HttpClientErrorException) {
String responseBodyAsString = ((HttpClientErrorException) e).getResponseBodyAsString();
Log.e(TAG, "Validation error" + responseBodyAsString);
//You can parse this with gson or jackson here
return responseBodyAsString;
}
}
return null;
}
@Override
protected void onPostExecute(String result) {
Log.d(TAG, "onPostExecute() called with: result = [" + result + "]");
}
}
Which prints in:
W/RestTemplate: POST request for "https://reqres.in/api/login" resulted in
400 (Bad Request); invoking error handler
E/MainActivity: 400 Bad Request
E/MainActivity: Validation error{"error":"Missing email or username"}
D/MainActivity: onPostExecute() called with: result = [{"error":"Missing email or username"}]
If you want to use the none default error handler and set your custom error handler you can get the error message as string this way:
restTemplate.setErrorHandler(new ResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return response.getStatusCode().is4xxClientError();
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
String errorResponse = new String(getResponseBody(response), getCharset(response).name());
Log.e(TAG, "handleError: called with: " + errorResponse);
}
});
private byte[] getResponseBody(ClientHttpResponse response) {
try {
InputStream responseBody = response.getBody();
if (responseBody != null) {
return FileCopyUtils.copyToByteArray(responseBody);
}
} catch (IOException ex) {
// ignore
}
return new byte[0];
}
private Charset getCharset(ClientHttpResponse response) {
HttpHeaders headers = response.getHeaders();
MediaType contentType = headers.getContentType();
return contentType != null ? contentType.getCharSet() : Charset.defaultCharset();
}
Then you can use Jackson or Gson to parse the error response as below:
new Gson().fromJson(responseBodyAsString, ExpectedResponse.class);
Note I've just did the same thing as implemented in DefaultResponseErrorHandler
Edit:
The whole AsyncTask
and Spring Android APIs are so outdated, Here is the same example with Retrofit:
api.login(new BodyModel("peter@klaven"))
.enqueue(new Callback<ExpectedModel>() {
@Override
public void onResponse(@NonNull Call<ExpectedModel> call, @NonNull Response<ExpectedModel> response) {
if (response.isSuccessful()) {
//Do what you got to do
} else {
Converter<ResponseBody, ErrorModel> converter = MainActivity.this.retrofit.responseBodyConverter(ErrorModel.class, new Annotation[0]);
ErrorModel errorModel = null;
try {
errorModel = converter.convert(response.errorBody());
Toast.makeText(MainActivity.this, errorModel.toString(), Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(@NonNull Call<ExpectedModel> call, @NonNull Throwable t) {
t.printStackTrace();
}
})
You can find the full gist in my github repo
Answered By - M. Reza Nasirloo
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.