Obtenha o código de status de resposta usando Retrofit 2.0 e RxJava

107

Estou tentando atualizar para Retrofit 2.0 e adicionar RxJava em meu projeto android. Estou fazendo uma chamada de api e desejo recuperar o código de erro em caso de uma resposta de erro do servidor.

Observable<MyResponseObject> apiCall(@Body body);

E na chamada 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
        }
    });

No Retrofit 1.9, o RetrofitError ainda existia e poderíamos obter o status fazendo:

error.getResponse().getStatus()

Como você faz isso com o Retrofit 2.0 usando RxJava?

AB
fonte

Respostas:

185

Em vez de declarar a chamada da API como você fez:

Observable<MyResponseObject> apiCall(@Body body);

Você também pode declará-lo assim:

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

Você terá então um Assinante como o seguinte:

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

Portanto, as respostas do servidor com um código de erro também serão entregues onNexte você pode obter o código chamando reponse.code().

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

EDIT: OK, finalmente consegui analisar o que o e-nouri disse em seu comentário, ou seja, que apenas os códigos 2xx o farão onNext. Acontece que ambos estamos certos:

Se a chamada for declarada assim:

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

ou mesmo isso

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

todas as respostas terminarão em onNext, independentemente do código de erro. Isso é possível porque tudo está embrulhado em um Responseobjeto pelo Retrofit.

Se, por outro lado, a chamada for declarada assim:

Observable<MyResponseObject> apiCall(@Body body);

ou isto

Observable<ResponseBody> apiCall(@Body body);

na verdade, apenas as respostas 2xx irão onNext. Todo o resto será embrulhado em HttpExceptione enviado para onError. O que também faz sentido, porque sem o Responseinvólucro, o que deve ser emitido onNext? Dado que o pedido não foi bem sucedido, a única coisa sensata a emitir seria null...

david.mihola
fonte
16
Para pessoas que procuram os códigos HTTP 4xx, eles acabarão como HttpException em onError. o onNext terá apenas o 2xx.
e-nouri
2
Isso é interessante ... Acabei de verificar novamente ( gist.github.com/DavidMihola/17a6ea373b9312fb723b ) e todos os códigos que experimentei acabaram onNextincluindo 404, etc. Usei Retrofit v2.0.0-beta3.
david.mihola
@ e-nouri: Acabei de adicionar um parágrafo à minha resposta que leva seu comentário em consideração!
david.mihola
1
@ david.mihola Se Build Retrofit via add GsonConverterFactory, assim como Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()), Como obter a resposta original?
Honghe.Wu
1
Além do tratamento de erros, acho que você pode querer inspecionar qualquer cabeçalho de resposta que veio junto com o corpo - isso também só é possível se você obtiver o Response. Também raramente o uso - mas acho bom saber que existe.
david.mihola
112

Dentro do método onError coloque-o para obter o código

((HttpException) e).code()
Diveno
fonte
7
Esta pode ser uma das respostas mais subestimadas que já vi no SO. Isso é gênio. Você pode fazer tudo o que for necessário com a resposta HttpException. Estou usando o RxJava e precisei analisar a resposta depois de receber um HTTP 400 BAD REQUEST. Muito obrigado!
GabrielOshiro
29
Apenas certifique-se de verificar se é uma instância de HttpException antes, porque NetworkErrors será IOExceptions e não possui um código de status.
Benjamin Mesing de
Seguindo em @BenjaminMesing, disse sobre NetworkError, se você estiver fazendo mais operadores downstream (map / flatMap / forEach etc.) em seu Rx antes de se inscrever, então pode haver uma série de possíveis tipos de exceção, e não necessariamente um erro em a solicitação de rede.
Mark Keen
java.lang.ClassCastException: java.lang.Throwable não pode ser convertido para retrofit2.HttpException
Someone Somewhere
7

Você deve notar que a partir do Retrofit2 todas as respostas com o código 2xx serão chamadas de retorno de chamada onNext () e o resto dos códigos HTTP como 4xx, 5xx serão chamados no retorno de chamada onError () , usando Kotlin. Eu vim com algo como isso no 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
fonte