Failed to parse error in Retrofit 2

I use Retrofit 2 and I get a null pointer exception on this line:

RetrofitClient.APIError error = RetrofitClient.ErrorUtils.parseError(response, retrofit); 

The error is null. More details:

This is the format in which the API returns an error:

 { "error": { "message": "Incorrect credentials", "statusCode": 401 } } 

Here is my login code:

 new Callback<LoginResponse>() { @Override public void onResponse(Response<LoginResponse> response, Retrofit retrofit) { if (listener != null) { if (response.isSuccess() && response.body() != null) { User user = RetrofitUserToUserMapper.fromRetrofitUser(response.body().getLoginUser()); } else { RetrofitClient.APIError error = RetrofitClient.ErrorUtils.parseError(response, retrofit); listener.onUserLoginFailure(error.getErrorMessage()); // NPE - error is null } } } @Override public void onFailure(Throwable t) { if (listener != null) { listener.onUserLoginFailure(""); } } } 

This is my Retrofit 2 class:

 public class RetrofitClient { public static final String API_ROOT = "http://example.com/api/v1/"; private static final String HEADER_OS_VERSION = "X-OS-Type"; private static final String HEADER_APP_VERSION = "X-App-Version"; private static final String HEADER_OS_VERSION_VALUE_ANDROID = "android"; private RetrofitClient() { } private static Retrofit INSTANCE; public static Retrofit getInstance() { if (INSTANCE == null) { setupRestClient(); } return INSTANCE; } public static void setupRestClient() { OkHttpClient httpClient = new OkHttpClient(); addHeadersRequiredForAllRequests(httpClient, BuildConfig.VERSION_NAME); INSTANCE = new Retrofit.Builder() .baseUrl(API_ROOT) .addConverterFactory(GsonConverterFactory.create()) .client(httpClient) .build(); } private static void addHeadersRequiredForAllRequests(OkHttpClient httpClient, final String appVersion) { class RequestInterceptor implements Interceptor { @Override public com.squareup.okhttp.Response intercept(Chain chain) throws IOException { Request request = chain.request().newBuilder() .addHeader(HEADER_OS_VERSION, HEADER_OS_VERSION_VALUE_ANDROID) .addHeader(HEADER_APP_VERSION, appVersion) .build(); return chain.proceed(request); } } httpClient.networkInterceptors().add(new RequestInterceptor()); } public static class ErrorUtils { public static APIError parseError(Response<?> response, Retrofit retrofit) { Converter<ResponseBody, APIError> converter = retrofit.responseConverter(APIError.class, new Annotation[0]); APIError error; try { error = converter.convert(response.errorBody()); } catch (IOException e) { e.printStackTrace(); return new APIError(); } catch (Exception e) { e.printStackTrace(); return new APIError(); } return error; } } public static class APIError { @SerializedName("error") public ErrorResponse loginError; public ErrorResponse getLoginError() { return loginError; } public String getErrorMessage() { return loginError.message; } private class ErrorResponse { @SerializedName("message") private String message; @SerializedName("statusCode") public int statusCode; public String getMessage() { return message; } public int getStatusCode() { return statusCode; } @Override public String toString() { return "LoginErrorResponseBody{" + "message='" + getMessage() + '\'' + ", statusCode=" + statusCode + '}'; } public void setMessage(String message) { this.message = message; } } } } 

I got the error utils class from this tutorial, but modified it a bit because the formatting of the errors in their example is different:

EDIT: this is the Converter class:

 /** * Convert objects to and from their representation as HTTP bodies. Register a converter with * Retrofit using {@link Retrofit.Builder#addConverterFactory(Factory)}. */ public interface Converter<F, T> { T convert(F value) throws IOException; abstract class Factory { /** * Create a {@link Converter} for converting an HTTP response body to {@code type} or null if it * cannot be handled by this factory. */ public Converter<ResponseBody, ?> fromResponseBody(Type type, Annotation[] annotations) { return null; } /** * Create a {@link Converter} for converting {@code type} to an HTTP request body or null if it * cannot be handled by this factory. */ public Converter<?, RequestBody> toRequestBody(Type type, Annotation[] annotations) { return null; } } } 
+5
source share
3 answers

UPDATE:

If you want to use a non-standard class instead of JSONObject below, you can refer to the following:

Custom class:

 public class ResponseError { Error error; class Error { int statusCode; String message; } } 

Add the following to the WebAPIService interface:

 @GET("/api/geterror") Call<ResponseError> getError2(); 

Then inside MainActivity.java :

 Call<ResponseError> responseErrorCall = service.getError2(); responseErrorCall.enqueue(new Callback<ResponseError>() { @Override public void onResponse(Response<ResponseError> response, Retrofit retrofit) { if (response.isSuccess() && response.body() != null){ Log.i(LOG_TAG, response.body().toString()); } else { if (response.errorBody() != null){ RetrofitClient.APIError error = RetrofitClient.ErrorUtils.parseError(response, retrofit); Log.e(LOG_TAG, error.getErrorMessage()); } } } @Override public void onFailure(Throwable t) { Log.e(LOG_TAG, t.toString()); } }); 

I just checked your RetrofitClient class using my web service. I made a small update for your APIError class as follows (add 2 constructors, they are not actually called):

 public APIError(){ this.loginError = new ErrorResponse(); } public APIError(int statusCode, String message) { this.loginError = new ErrorResponse(); this.loginError.statusCode = statusCode; this.loginError.message = message; } 

Interface:

 public interface WebAPIService { @GET("/api/geterror") Call<JSONObject> getError(); } 

MainActivity:

 // Retrofit 2.0-beta2 Retrofit retrofit = new Retrofit.Builder() .baseUrl(API_URL_BASE) .addConverterFactory(GsonConverterFactory.create()) .build(); WebAPIService service = retrofit.create(WebAPIService.class); Call<JSONObject> jsonObjectCall = service.getError(); jsonObjectCall.enqueue(new Callback<JSONObject>() { @Override public void onResponse(Response<JSONObject> response, Retrofit retrofit) { if (response.isSuccess() && response.body() != null){ Log.i(LOG_TAG, response.body().toString()); } else { if (response.errorBody() != null){ RetrofitClient.APIError error = RetrofitClient.ErrorUtils.parseError(response, retrofit); Log.e(LOG_TAG, error.getErrorMessage()); } } } @Override public void onFailure(Throwable t) { Log.e(LOG_TAG, t.toString()); } }); 

My web service (Asp.Net API):

According to your JSON response data, I used the following code:

 [Route("api/geterror")] public HttpResponseMessage GetError() { var detailError = new { message = "Incorrect credentials", statusCode = 401 }; var myError = new { error = detailError }; return Request.CreateResponse(HttpStatusCode.Unauthorized, myError); } 

It works! Hope this helps!

+5
source

The problem was really strange, and I can’t explain how this happens, and my solution is really ugly.

There were actually two converter factories at the revisions, and the factory converter, which he returned when I asked him to convert the answer, was zero.

I found out about this while I was debugging the retooling method that returns converter factories. The second factory that he returned really successfully completed the conversion, and I was finally able to parse my answer. I still don’t know what I did wrong.

Here is what turned out to be my ugly solution:

 String errorMessage = ""; for (Converter.Factory factory : retrofit.converterFactories()) { try { LoginError loginError = (LoginError) factory.fromResponseBody(LoginError.class, new Annotation[0]).convert(errorBody); if (loginError != null) { errorMessage = loginError.error.message; } } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } if (!TextUtils.isEmpty(errorMessage)) { listener.onUserLoginFailure(errorMessage); } 

For the first time in the cycle, I got NPE. The second time I received an error message

Here is the error class in which I ended up:

 private class LoginError { Error error; class Error { String message; int statusCode; } } 

EDIT: I believe that the reason for the failure of WildCard may be the redefinition of which converter will return, the one I transmitted here:

 public static APIError parseError(Response<?> response, Retrofit retrofit) { 
+1
source
 @Override public void onResponse(Response<User> response, Retrofit retrofit) { if (response.isSuccess()) { User user = response.body; Log.e("User name", user.getName()); // do whatever you want }else{ Converter<GlobalErrorObject> converter = (Converter<GlobalErrorObject>) GsonConverterFactory.create().get(GlobalErrorObject.class); try { GlobalErrorObject globalErrorObject = converter.fromBody(response.errorBody()); Log.e("Error", globalErrorObject.getErrorMessage()); } catch (IOException e) { e.printStackTrace(); } } } 

** In my case, GlobalErrorObject is a pojo representing JSON as:

 { "errorCode": "API_INVALID_TOKEN", "errorType": "API_ERROR", "errorMessage": "Valid API Token required." } 

** This code works for me.

0
source

Source: https://habr.com/ru/post/1239196/


All Articles