Операторы обработки переопределения 2 и RxJava

Я использую Retrofit 2 в своем проекте с интерфейсом Observable и обертой результата. Пример:

@POST("api/login")
Observable<Result<LoginResponse>> login(@Body LoginRequest request);

Мне нужна обертка результатов, чтобы получить больше информации из ответа, чем просто сериализованный объект (например, заголовки, http status...).

Проблема заключается в том, что с оберткой результата исключение вызывает сетевой вызов. Вы можете найти исключение внутри результата, вызвав Result.error().

Что делать, если я хочу воспользоваться операторами ошибок RxJava? Например, я хотел бы использовать оператор повтора для сетевой ошибки, но оператор повторения работает только в том случае, если наблюдаемое исключение генерируется исключением.

Ответы

Ответ 1

Вот решение, с которым я столкнулся. Если я его улучшу, я опубликую здесь изменения.

Решение моей проблемы (исключение, проглатываемое Retrofit и не обработанное RxJava), - это метод Observable.error, который создает новое наблюдаемое, которое только испускает ошибку, поэтому я могу "реконструировать" исключение.

Я создал наблюдаемый трансформатор для добавления к любому остальному вызову, который испускает модификацию. Результат. Этот трансформатор принимает Observable > и, если ответ не имеет ошибок, преобразует его в Observable > . Если есть ошибки, он возвращает Observable.error с пользовательскими исключениями Http *, которые я могу обработать позже в моем Observer в обратном вызове onError. Я ставлю его как статический метод класса утилиты, который называется ObservableTransformations.resultToResponseWithHttpErrorHandling.

Вот он:

public class ObservableTransformations {

public static <T> Observable.Transformer<Result<T>, Response<T>> resultToResponseWithHttpErrorHandling() {
    return observable -> observable.flatMap(r -> {
        Observable<Response<T>> returnObservable = Observable.just(r.response());
        if (r.isError()) {
            Throwable throwable = r.error();
            if (throwable instanceof IOException) {
                Timber.e(throwable, "Retrofit connection error.");
                // TODO Check this cases
                if (throwable instanceof java.net.ConnectException) {
                    returnObservable = Observable.error(new HttpNoInternetConnectionException());
                } else if (throwable instanceof SocketTimeoutException) {
                    returnObservable = Observable.error(new HttpServerDownException());
                } else {
                    returnObservable = Observable.error(new HttpNoInternetConnectionException());
                }
            } else {
                Timber.e(throwable, "Retrofit general error - fatal.");
                returnObservable = Observable.error(new HttpGeneralErrorException(r.error()));
            }
        } else {
            Response<T> retrofitResponse = r.response();
            if (!retrofitResponse.isSuccess()) {
                int code = retrofitResponse.code();
                String message = "";
                try {
                    message = retrofitResponse.errorBody().string();
                } catch (IOException e) {
                    Timber.e(e, "Error reading errorBody from response");
                }
                Timber.i("Server responded with error. Code: " + code + " message: " + message);
                Throwable t = null;
                if (NetworkUtils.isClientError(code)) {
                    t = new HttpClientException(retrofitResponse.code(), message);
                } else if (NetworkUtils.isServerError(code)) {
                    t = new HttpServerErrorException(retrofitResponse.code(), message);
                }
                returnObservable = Observable.error(t);
            }
        }
        return returnObservable;
    }).retryWhen(new RetryWithDelayIf(3, 1000, t -> {
        return (t instanceof HttpNoInternetConnectionException) || (t instanceof HttpServerDownException);
    }));
}

}

Повторение выполняется 3 раза с использованием экспоненциального отсрочка, и только если исключение представляет собой HttpNoInternetConnectionException или HttpServerDownException.

Здесь находится класс RetryWithDelayIf. Требуется выполнение условия для повторения в качестве последнего аргумента конструктора (функция, принимающая throwable и возвращающая true, если этот throwable должен запускать повтор и false, если нет).

public class RetryWithDelayIf implements
    Func1<Observable<? extends Throwable>, Observable<?>> {

private final int maxRetries;
private final int retryDelayMillis;
private int retryCount;
private Func1<Throwable, Boolean> retryIf;

public RetryWithDelayIf(final int maxRetries, final int retryDelayMillis, Func1<Throwable, Boolean> retryIf) {
    this.maxRetries = maxRetries;
    this.retryDelayMillis = retryDelayMillis;
    this.retryCount = 0;
    this.retryIf = retryIf;
}

@Override
public Observable<?> call(Observable<? extends Throwable> attempts) {
    return attempts.zipWith(Observable.range(1, maxRetries + 1), (n, i) -> {
        return new Tuple<Throwable, Integer>(n, i);
    })
            .flatMap(
                    ni -> {
                        if (retryIf.call(ni.getFirst()) && ni.getSecond() <= maxRetries) {
                            return Observable.timer((long) Math.pow(2, ni.getSecond()), TimeUnit.SECONDS);
                        } else {
                            return Observable.error(ni.getFirst());
                        }
                    });
}

}

Наконец, здесь используется вызов restService:

restService.login(new LoginRestRequest(username, password))
                .compose(ObservableTransformations.resultToResponseWithHttpErrorHandling());

В onError вашего наблюдателя вы можете, наконец, обработать исключения Http *.

Ответ 2

Вы должны проверить, является ли Throwable throw экземпляром HttpException.