Получить код статуса ответа с помощью Retrofit 2.0 и RxJava

110

Я пытаюсь перейти на Retrofit 2.0 и добавить RxJava в свой проект Android. Я делаю вызов api и хочу получить код ошибки в случае ответа с ошибкой от сервера.

Observable<MyResponseObject> apiCall(@Body body);

И в вызове 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
        }
    });

В Retrofit 1.9 ошибка RetrofitError все еще существовала, и мы могли получить статус, выполнив:

error.getResponse().getStatus()

Как это сделать с помощью Retrofit 2.0 с использованием RxJava?

AB
источник

Ответы:

187

Вместо того, чтобы объявлять вызов API, как вы:

Observable<MyResponseObject> apiCall(@Body body);

Вы также можете объявить это так:

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

Тогда у вас будет подписчик, подобный следующему:

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

Таким образом, ответы сервера с кодом ошибки также будут доставлены, onNextи вы можете получить код, позвонив reponse.code().

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

РЕДАКТИРОВАТЬ: ОК, я наконец-то дошел до того, что сказал e-nouri в своем комментарии, а именно, что только коды 2xx будут onNext. Оказывается, мы оба правы:

Если вызов объявлен так:

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

или даже это

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

все ответы попадут в него onNext, независимо от их кода ошибки. Это возможно, потому что все обернуто в Responseобъект с помощью Retrofit.

Если, с другой стороны, вызов объявлен так:

Observable<MyResponseObject> apiCall(@Body body);

или это

Observable<ResponseBody> apiCall(@Body body);

действительно только ответы 2xx пойдут в onNext. Все остальное будет упаковано HttpExceptionи отправлено по адресу onError. Что также имеет смысл, потому что без Responseоболочки, что должно быть отправлено onNext? Учитывая, что запрос не был успешным, единственная разумная вещь, которую нужно отправить, это null...

Дэвид Михола
источник
17
Для людей, которые ищут HTTP-коды 4xx, они окажутся HttpException в onError. onNext будет иметь только 2xx.
e-nouri
2
Это интересно ... Я только что проверил еще раз ( gist.github.com/DavidMihola/17a6ea373b9312fb723b ), и все коды, которые я пробовал, оказались в onNextтом числе 404 и т. Д. Я использовал Retrofit v2.0.0-beta3.
david.mihola
@ e-nouri: Я только что добавил к своему ответу абзац, в котором учтен ваш комментарий!
david.mihola
1
@ david.mihola Если Build Retrofit через add GsonConverterFactory, точно так же Retrofit retrofit = new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()), как получить исходный ответ?
Honghe.Wu
1
Помимо обработки ошибок, я думаю, вы, возможно, захотите проверить любые заголовки ответов, которые идут вместе с телом - это также возможно только в том случае, если вы получите файл Response. Я тоже редко им пользуюсь - но думаю, хорошо знать, что он существует.
david.mihola
112

Внутри метода onError поместите это, чтобы получить код

((HttpException) e).code()
Дивено
источник
7
Это может быть один из самых недооцененных ответов, которые я когда-либо видел в SO. Это гениально. Вы можете сделать все, что вам нужно, с ответом HttpException. Я использую RxJava, и мне нужно было проанализировать ответ после получения файла HTTP 400 BAD REQUEST. Большое спасибо!
GabrielOshiro
29
Просто убедитесь, что вы проверили, является ли это instanceof HttpException, потому что NetworkErrors будет IOExceptions и не будет иметь кода состояния.
Бенджамин Месинг
Чтобы следовать из @BenjaminMesing, сказанного о NetworkError, если вы выполняете больше последующих операторов (map / flatMap / forEach и т. Д.) В своем Rx перед подпиской, тогда может быть множество возможных типов исключений, и не обязательно ошибка на сетевой запрос.
Марк Кин
java.lang.ClassCastException: java.lang.Throwable не может быть преобразован в retrofit2.HttpException
Someone Somewhere
7

Вы должны отметить, что с Retrofit2 все ответы с кодом 2xx будут вызываться из обратного вызова onNext (), а остальные HTTP-коды, такие как 4xx, 5xx, будут вызываться из обратного вызова onError () , используя Kotlin, я придумал что-то вроде это в 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))
  }
Мохаммад Реза
источник