localhost
localhost

Reputation: 5598

Implement retryWhen logic

I have an app which requires session (cookies) to process web calls. Im using Retrofit+RxJava. However, session could expire (Retrofit error with 401 Unauthorized status) and i want to reauthenticate (to get fresh cookies) and retry previous call in this case. How would i do it with RxJava?

My example:

getServerApi().getDialogs(offset, getCookies())
     .subscribeOn(Schedulers.newThread())
     .observeOn(AndroidSchedulers.mainThread())
     .retryWhen(observable -> {...}) // Need some logic
     .subscribe(dialogsEnvelope -> getView().setDialogs(dialogsEnvelope),
                throwable -> getView().setError(processFail(throwable)));

Upvotes: 7

Views: 1666

Answers (3)

david.mihola
david.mihola

Reputation: 13012

While an Interceptor may be a better solution for this particular problem, the question specifically asked for a solution using retryWhen, so here is is one way to do it:

retryWhen(new Func1<Observable<Throwable>, Observable<?>>(){

    @Override
    public void Observable<?> call(Observable<Throwable>> attempts) {
        return attempts.flatMap(new Func1<Throwable, Observable<?>>() {

            @Override
            public Observable<?> call(Throwable throwable) {
                 if (throwable instanceof RetrofitError) {
                     RetrofitError retrofitError = (RetrofitError) throwable;
                     if (retrofitError.getKind() == RetrofitError.Kind.HTTP && retrofitError.getResponse().getStatus() == 401) {
                         // this is the error we care about - to trigger a retry we need to emit anything other than onError or onCompleted
                         return Observable.just(new Object());
                     } else {
                         // some other kind of error: just pass it along and don't retry
                         return Observable.error(throwable);
                     }
                 } else {
                     // some other kind of error: just pass it along and don't retry
                     return Observable.error(throwable);
                 }
             }
        });
    }
})

However, your getCookies would not be called again in the case of a simple retry. That would just resubscribe to the same Observable but getCookies was called before the creation of that Observable. So I think you would have to wrap the creation of the source Observable in a defer.

Upvotes: 8

localhost
localhost

Reputation: 5598

While browsing the Internet for discovering a proper answer - i've found this cool gist describing how to refresh OAuth token with the help of OkHttp Interceptor (similar to accepted answer, but more complete).

It has nothing to do with RxJava, but for me it's more acceptable because i don't have to wrap each Observable with retryWith logic - everything is done on lower level (OkHttp library).

Upvotes: 0

Adel Nizamuddin
Adel Nizamuddin

Reputation: 821

Use OkHttp's extremely powerful Interceptor.

public class RecoverInterceptor implements Interceptor {
  String getAuth() {
    // check if we have auth, if not, authorize
    return "Bearer ...";
  }

  void clearAuth() {
    // clear everything
  }

  @Override public Response intercept(Chain chain) throws IOException {
    final Request request = chain.request();
    if (request.urlString().startsWith("MY ENDPOINT")) {
      final Request signed = request.newBuilder()
          .header("Authorization", getAuth())
          .build();
      final Response response = chain.proceed(signed);
      if (response.code() == 401) {
        clearAuth();
        return intercept(chain);
      } else {
        return response;
      }
    } else {
      return chain.proceed(request);
    }
  }
}

Remember to synchronize your auth process code, so that two concurrent requests do not invoke it at the same time.

Upvotes: 4

Related Questions