Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)));
like image 403
localhost Avatar asked May 28 '15 21:05

localhost


People also ask

What is a retry logic?

Configurable Retry Logic is an eCommerce platform feature that recovers failed transactions due to soft declines.


2 Answers

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.

like image 133
david.mihola Avatar answered Nov 11 '22 01:11

david.mihola


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.

like image 26
Adel Nizamutdinov Avatar answered Nov 11 '22 00:11

Adel Nizamutdinov