Uzyskaj kod statusu odpowiedzi za pomocą Retrofit 2.0 i RxJava

108

Próbuję uaktualnić do wersji Retrofit 2.0 i dodać RxJava do mojego projektu na Androida. Wykonuję wywołanie API i chcę pobrać kod błędu w przypadku odpowiedzi błędu z serwera.

Observable<MyResponseObject> apiCall(@Body body);

A w wywołaniu 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
        }
    });

W Retrofit 1.9 błąd RetrofitError nadal istniał i mogliśmy go uzyskać wykonując:

error.getResponse().getStatus()

Jak to zrobić z Retrofit 2.0 przy użyciu RxJava?

AB
źródło

Odpowiedzi:

185

Zamiast deklarować wywołanie API, tak jak to zrobiłeś:

Observable<MyResponseObject> apiCall(@Body body);

Możesz również zadeklarować to w ten sposób:

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

Będziesz mieć subskrybenta, takiego jak następujący:

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

Tak więc odpowiedzi serwera z kodem błędu również zostaną dostarczone do onNexti możesz uzyskać kod, dzwoniąc reponse.code().

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

EDYCJA: OK, w końcu zajrzałem do tego, co powiedział e-nouri w swoim komentarzu, a mianowicie, że tylko kody 2xx będą onNext. Okazuje się, że oboje mamy rację:

Jeśli wywołanie jest zadeklarowane w następujący sposób:

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

a nawet to

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

wszystkie odpowiedzi zakończą się onNext, niezależnie od kodu błędu. Jest to możliwe, ponieważ Responsedzięki Retrofitowi wszystko jest opakowane w obiekt.

Jeśli, z drugiej strony, wezwanie zostanie zadeklarowane w następujący sposób:

Observable<MyResponseObject> apiCall(@Body body);

albo to

Observable<ResponseBody> apiCall(@Body body);

w rzeczywistości tylko odpowiedzi 2xx trafią do onNext. Wszystko inne zostanie zapakowane HttpExceptioni wysłane na adres onError. Co też ma sens, ponieważ bez Responseopakowania, do czego powinno być emitowane onNext? Biorąc pod uwagę, że żądanie się nie powiodło, jedyną rozsądną rzeczą do wyemitowania byłoby null...

david.mihola
źródło
17
Dla osób szukających kodów HTTP 4xx będą one miały postać HttpException w onError. onNext będzie miał tylko 2xx.
e-nouri
2
To ciekawe ... Właśnie ponownie sprawdziłem ( gist.github.com/DavidMihola/17a6ea373b9312fb723b ) i wszystkie kody, które wypróbowałem, kończyły się w onNexttym 404 itd. Użyłem Retrofit v2.0.0-beta3.
david.mihola
@ e-nouri: Właśnie dodałem akapit do mojej odpowiedzi, który uwzględnia Twój komentarz!
david.mihola
1
@ david.mihola Jeśli Build Retrofit przez add GsonConverterFactory, tak jak Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()), jak uzyskać oryginalną odpowiedź?
Honghe.Wu
1
Oprócz obsługi błędów, myślę, że możesz chcieć mieć możliwość sprawdzenia wszelkich nagłówków odpowiedzi, które pojawiły się wraz z treścią - jest to również możliwe tylko wtedy, gdy otrzymasz plik Response. Rzadko go też używam - ale myślę, że dobrze jest wiedzieć, że istnieje.
david.mihola
112

Wewnątrz metody onError umieść to, aby pobrać kod

((HttpException) e).code()
Diveno
źródło
7
To może być jedna z najbardziej niedocenianych odpowiedzi, jakie kiedykolwiek widziałem w SO. To jest genialne. Możesz zrobić wszystko, co trzeba zrobić z odpowiedzią HttpException. Używam RxJava i musiałem przeanalizować odpowiedź po otrzymaniu pliku HTTP 400 BAD REQUEST. Dziękuję Ci bardzo!
GabrielOshiro
29
Po prostu upewnij się, że wcześniej sprawdziłeś, czy jest to wystąpienie HttpException, ponieważ NetworkErrors będą IOExceptions i nie będą miały kodu stanu.
Benjamin Mesing
Kontynuując od @BenjaminMesing, który powiedział o NetworkError, jeśli robisz więcej operatorów podrzędnych (map / flatMap / forEach itp.) W swoim Rx przed zapisaniem się, może istnieć wiele możliwych typów wyjątków i niekoniecznie błąd na żądanie sieciowe.
Mark Keen
java.lang.ClassCastException: java.lang.Throwable nie można rzutować na retrofit2.HttpException
Someone Somewhere
7

Należy pamiętać, że od Retrofit2 wszystkie odpowiedzi z kodem 2xx zostanie wywołana z onNext () callback a resztą HTTP kody jak 4xx, 5xx będzie nazwany na onError () zwrotnego, wykorzystując Kotlin Mam wymyślił coś podobnego to w 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
źródło