Модернизация с использованием rxjava сетевых исключений в глобальном масштабе
Я пытаюсь обрабатывать исключения в приложении на глобальном уровне, так что retrofit выдает ошибку, которую я улавлю в определенном классе с логикой для обработки этих ошибок.
У меня есть интерфейс
@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 для всех вызовов отдыха с каким-то наблюдаемым, который поймает эту ошибку во всем мире, обработает ее и повторит вызов, который его выбрал?
На данный момент я использую тему, чтобы поймать ошибку на .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
}
);
но я не могу повторить вызов наблюдаемого, который выдает ошибку.
Ответы
Ответ 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 not recoverable from here
return Observable.error(throwable);
}
};
}
Отметим также, что эта функция может быть легко использована в других случаях, поскольку она не набирается с фактическими значениями, испускаемыми возобновленным Observable.
Ответ 2
@Override
public Observable<List<MessageEntity>> messages(String accountId, int messageType) {
return mMessageService.getLikeMessages(messageType)
.onErrorResumeNext(mTokenTrick.
refreshTokenAndRetry(mMessageService.getLikeMessages(messageType)));
}