No se puede analizar el error en Retrofit 2

Estoy utilizando Retrofit 2 y estoy recibiendo una excepción de puntero nulo en esta línea:

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

El error es nulo. Más detalles:

Este es el formato en el que la API devuelve el error en:

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

Aquí está mi código de devolución de llamada de acceso:

 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(""); } } } 

Esta es mi clase de Retrofit 2:

 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; } } } } 

Tengo el error utils clase de este tutorial, pero lo cambió un poco porque el formato de error en su ejemplo es diferente:

EDIT: Esta es la clase Converter :

 /** * 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; } } } 

ACTUALIZAR:

Si desea utilizar una clase personalizada en lugar de JSONObject continuación, puede hacer referencia a lo siguiente:

Clase personalizada:

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

Agregue lo siguiente a la interfaz WebAPIService :

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

Luego, dentro de 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()); } }); 

Acabo de probar tu clase RetrofitClient con mi servicio web. Hice una pequeña actualización a su clase APIError como la siguiente (agregue 2 constructores, en realidad no se llaman):

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

Interfaz:

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

Actividad principal:

 // 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()); } }); 

Mi servicio web (Asp.Net Web API):

De acuerdo con sus datos de respuesta JSON, he utilizado el código siguiente:

 [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); } 

¡Esta funcionando! ¡Espero eso ayude!

El problema era realmente extraño y no puedo explicar cómo estaba sucediendo esto, y mi solución es realmente fea.

Retrofit en realidad tenía dos fábricas de conversión y la fábrica de convertidores que volvió cuando le pregunté a convertir la respuesta, fue nulo.

Me enteré de esto mientras estaba depurando el método de retrofit que devuelve las fábricas del convertidor. La segunda fábrica que regresó realmente hizo la conversión con éxito y finalmente pude analizar mi respuesta. Todavía no sé lo que hice mal.

Esto es lo que terminó siendo mi fea solución:

 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); } 

La primera vez en el bucle me dieron un NPE. La segunda vez que recibí el mensaje de error

Esta es la clase de error que acabé usando:

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

EDIT: Supongo que podría ser la culpa del WildCard para confundir retrofit lo que el convertidor de volver, el que estaba pasando aquí:

 public static APIError parseError(Response<?> response, Retrofit retrofit) { 
 @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(); } } } 

** En mi caso, GlobalErrorObject es un pojo que representa a JSON como:

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

** El código anterior funciona para mí.

  • Encadenamiento de solicitudes en Retrofit + RxJava
  • Caché de datos de respuesta con retrofit2 + okhttp3
  • ¿Es posible la llamada a SOAP webservice usando Retrofit?
  • % 3F en lugar de un signo de interrogación en mi URL de actualización
  • Prueba de unidad Retrofit api call con Mockito - ArgumentCaptor
  • Retrofit 2 (en Android) después de la implementación, ¿Dónde están mis elementos JSON?
  • Runtime Execption java.lang.NoClassDefFoundError: retrofit2.Platform en Android
  • Establecimiento de una etiqueta en una llamada de Retrofit para cancelar por etiqueta más tarde
  • ¿Cómo obtener la url de la solicitud en retrofit 2.0 con rxjava?
  • Cómo pasar cadena en 'Body' Parámetro de Retrofit 2 en android
  • Retrofit2 (interceptor) + GoogleApiClient cómo actualizar token
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.