модернизация с помощью rxjava, обрабатывающего сетевые исключения в глобальном масштабе

Я пытаюсь обрабатывать исключения в приложении на глобальном уровне, поэтому модификация выдает ошибку, которую я улавливаю в каком-то конкретном классе с логикой для обработки этих ошибок.

У меня есть интерфейс

@POST("/token")
AuthToken refreshToken(@Field("grant_type") String grantType, @Field("refresh_token") String refreshToken);

и наблюдаемые

/**
 * Refreshes auth token
 *
 * @param refreshToken
 * @return
 */
public Observable<AuthToken> refreshToken(String refreshToken) {
    return Observable.create((Subscriber<? super AuthToken> subscriber) -> {
        try {
            subscriber.onNext(apiManager.refreshToken(REFRESH_TOKEN, refreshToken));
            subscriber.onCompleted();
        } catch (Exception e) {
            subscriber.onError(e);
        }
    }).subscribeOn(Schedulers.io());
}

Когда я получаю 401 с сервера (неверный токен или какая-то другая ошибка, связанная с сетью), я хочу обновить токен и повторить остальной вызов. Есть ли способ сделать это с помощью rxjava для всех вызовов rest с какой-либо наблюдаемой, которая будет перехватывать эту ошибку глобально, обрабатывать ее и повторять вызов, который ее вызвал?

На данный момент я использую тему, чтобы поймать ошибку в .subscribe (), как это

private static BehaviorSubject errorEvent = BehaviorSubject.create();

public static BehaviorSubject<RetrofitError> getErrorEvent() {
    return errorEvent;
}

и в каком-то звонке

getCurrentUser = userApi.getCurrentUser().observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                    (user) -> {
                        this.user = user;
                    },
                    errorEvent::onNext
            );

затем в своей основной деятельности я подписываюсь на эту тему поведения и анализирую ошибку

SomeApi.getErrorEvent().subscribe(
            (e) -> {
                //parse the error
            }
    );

но я не могу повторить вызов наблюдаемого, которое вызывает ошибку.


person ddog    schedule 05.10.2014    source источник
comment
Можете ли вы показать, как вы прямо сейчас связываете refreshToken() наблюдаемую с другими вызовами?   -  person a.bertucci    schedule 05.10.2014


Ответы (1)


Вам необходимо использовать оператор onErrorResumeNext(Func1 resumeFunction), более подробно описанный в официальная вики:

Метод onErrorResumeNext () возвращает Observable, который отражает поведение исходного Observable, если только этот Observable не вызывает onError (), и в этом случае вместо распространения этой ошибки на подписчика вместо этого начнется onErrorResumeNext () зеркальное копирование второй, резервной копии Observable

В вашем случае я бы поставил примерно так:

getCurrentUser = userApi.getCurrentUser()
.onErrorResumeNext(refreshTokenAndRetry(userApi.getCurrentUser()))
.observeOn(AndroidSchedulers.mainThread())
            .subscribe(...)

где:

    private <T> Func1<Throwable,? extends Observable<? extends T>> refreshTokenAndRetry(final Observable<T> toBeResumed) {
        return new Func1<Throwable, Observable<? extends T>>() {
            @Override
            public Observable<? extends T> call(Throwable throwable) {
                // Here check if the error thrown really is a 401
                if (isHttp401Error(throwable)) {
                    return refreshToken().flatMap(new Func1<AuthToken, Observable<? extends T>>() {
                        @Override
                        public Observable<? extends T> call(AuthToken token) {
                            return toBeResumed;
                        }
                    });
                }
                // re-throw this error because it's not recoverable from here
                return Observable.error(throwable);
            }
        };
    }

Также обратите внимание, что эту функцию можно легко использовать в других случаях, потому что она не типизирована с фактическими значениями, выдаваемыми возобновленным Observable.

person a.bertucci    schedule 05.10.2014
comment
В дополнение к приведенному выше ответу я бы использовал оператор defer(), чтобы не повторять запрос со старым наблюдаемым. Код: onErrorResumeNext(refreshTokenAndRetry(Observable.defer(() -> userApi.getCurrentUser()))). Надеюсь кому то пригодится :) - person blizzard; 25.10.2015
comment
@blizzard спасибо за ваш комментарий. Оказалось, что в моем случае очень важно ваше предложение - person Michael Katkov; 04.08.2017