Obtenga el código de estado de respuesta usando Retrofit 2.0 y RxJava

107

Estoy intentando actualizar a Retrofit 2.0 y agregar RxJava en mi proyecto de Android. Estoy haciendo una llamada a la API y quiero recuperar el código de error en caso de una respuesta de error del servidor.

Observable<MyResponseObject> apiCall(@Body body);

Y en la llamada RxJava:

myRetrofitObject.apiCall(body).subscribe(new Subscriber<MyResponseObject>() {
        @Override
        public void onCompleted() {

        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onNext(MyResponseObject myResponseObject) {
           //On response from server
        }
    });

En Retrofit 1.9, RetrofitError todavía existía y podíamos obtener el estado haciendo:

error.getResponse().getStatus()

¿Cómo se hace esto con Retrofit 2.0 usando RxJava?

AB
fuente

Respuestas:

185

En lugar de declarar la llamada a la API como lo hizo:

Observable<MyResponseObject> apiCall(@Body body);

También puedes declararlo así:

Observable<Response<MyResponseObject>> apiCall(@Body body);

A continuación, tendrá un suscriptor como el siguiente:

new Subscriber<Response<StartupResponse>>() {
    @Override
    public void onCompleted() {}

    @Override
    public void onError(Throwable e) {
        Timber.e(e, "onError: %", e.toString());

        // network errors, e. g. UnknownHostException, will end up here
    }

    @Override
    public void onNext(Response<StartupResponse> startupResponseResponse) {
        Timber.d("onNext: %s", startupResponseResponse.code());

        // HTTP errors, e. g. 404, will end up here!
    }
}

Por lo tanto, las respuestas del servidor con un código de error también se enviarán onNexty puede obtener el código llamando a reponse.code().

http://square.github.io/retrofit/2.x/retrofit/retrofit/Response.html

EDITAR: OK, finalmente pude investigar lo que e-nouri dijo en su comentario, es decir, que solo los códigos 2xx lo harán onNext. Resulta que ambos tenemos razón:

Si la llamada se declara así:

Observable<Response<MyResponseObject>> apiCall(@Body body);

o incluso esto

Observable<Response<ResponseBody>> apiCall(@Body body);

todas las respuestas terminarán en onNext, independientemente de su código de error. Esto es posible porque todo está envuelto en un Responseobjeto por Retrofit.

Si, por el contrario, la llamada se declara así:

Observable<MyResponseObject> apiCall(@Body body);

o esto

Observable<ResponseBody> apiCall(@Body body);

de hecho, solo las respuestas 2xx irán a onNext. Todo lo demás será envuelto en un HttpExceptiony enviado a onError. Lo que también tiene sentido, porque sin la Responseenvoltura, ¿a qué se debería emitir onNext? Dado que la solicitud no tuvo éxito, lo único sensato a emitir sería null...

david.mihola
fuente
16
Para las personas que buscan los códigos HTTP 4xx, terminarán como HttpException en onError. onNext solo tendrá el 2xx.
e-nouri
2
Eso es interesante ... Acabo de comprobar de nuevo ( gist.github.com/DavidMihola/17a6ea373b9312fb723b ) y todos los códigos que probé terminan onNextincluyendo 404, etc. Usé Retrofit v2.0.0-beta3.
david.mihola
@ e-nouri: ¡Acabo de agregar un párrafo a mi respuesta que tiene en cuenta su comentario!
david.mihola
1
@ david.mihola Si Build Retrofit via add GsonConverterFactory, al igual que Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()), ¿Cómo obtener la respuesta original?
Honghe.Wu
1
Además del manejo de errores, creo que es posible que desee poder inspeccionar los encabezados de respuesta que vinieron con el cuerpo; eso también solo es posible si obtiene el Response. Rara vez lo uso también, pero creo que es bueno saber que existe.
David.mihola
112

Dentro del método onError pon esto para obtener el código

((HttpException) e).code()
Diveno
fuente
7
Esta podría ser una de las respuestas más subestimadas que he visto en SO. Esto es genial. Puede hacer todo lo que necesite hacer con la respuesta con HttpException. Estoy usando RxJava y necesitaba analizar la respuesta después de recibir un HTTP 400 BAD REQUEST. ¡Muchas gracias!
GabrielOshiro
29
Solo asegúrese de verificar si es una instancia de HttpException antes porque NetworkErrors será IOExceptions y no tendrá un código de estado.
Benjamin Mesing
Para seguir a @BenjaminMesing dijo sobre NetworkError, si está haciendo más operadores descendentes (map / flatMap / forEach, etc.) en su Rx antes de suscribirse, entonces podría haber una gran cantidad de posibles tipos de excepciones, y no necesariamente un error en la solicitud de red.
Mark Keen
java.lang.ClassCastException: java.lang.Throwable no se puede convertir en retrofit2.HttpException
Someone Somewhere
7

Debe tener en cuenta que a partir de Retrofit2, todas las respuestas con el código 2xx se llamarán desde la devolución de llamada onNext () y el resto de los códigos HTTP como 4xx, 5xx se llamarán en la devolución de llamada onError () , usando Kotlin, se me ocurrió algo como esto en el onError () :

mViewReference?.get()?.onMediaFetchFinished(downloadArg)
  if (it is HttpException) {
    val errorCode = it.code()
    mViewReference?.get()?.onMediaFetchFailed(downloadArg,when(errorCode){
      HttpURLConnection.HTTP_NOT_FOUND -> R.string.check_is_private
      else -> ErrorHandler.parseError(it)
    })
  } else {
    mViewReference?.get()?.onMediaFetchFailed(downloadArg, ErrorHandler.parseError(it))
  }
MohammadReza
fuente