How to Retry Retrofit Call on Http Errors (401) When Using Rxjava

Rxjava - How to retry an API call with different params while calling two APIs in parallel

U can use onErrorResumeNext to handle ur scenario here. For both the API calls add a onErrorResumeBlock which could be retrying the same api with diff params.
Like:

Single.zip(API1.subscribeOn(Schedulers.io())
.onErrorResumeNext { throwable:Throwable ->
return@onErrorResumeNext API1DIFF_PARAMS },
API2.subscribeOn(Schedulers.io())
.onErrorResumeNext { throwable:Throwable ->
return@onErrorResumeNext API2DIFF_PARAMS }, Bifunction())).subscribe();

Downside is this will work only once. If your second API also fails then it will land in the error block of ur subscribe method

Refreshing access token when REST API returns 401 in Retrofit2 RxJava

Use Authenticator API of retrofit and inside this call access token api to get access token and re try the fail API call using this access token.

How do I handle HTTP errors like 401, 403, 503,500 using RxJava Observer instead of Event Bus

one approach i've used in the past is to share a Subject used for communicating error conditions.

  • on the producer side, hold a reference to it a Subject type (Publish/Behavior/Replay/etc) so as to invoke onNext() with the next error the application has incurred. in this case the producer would be the ResponseCodeCheckInterceptor instance. rather than throwing the various exceptions, this instance would instead emit an ErrorState describing the error that just occurred. (assume ErrorState to be a custom type that carries just enough information about an error condition for consumers to decide how to react, e.g. update the UI, clean-up resources, etc).

  • on the consumer side, hold a reference to the shared Subject as an Observable<ErrorState>. as you seem to be doing MVP, the Presenter would likely be one of your consumers.

(dependency injection is a good way to go about sharing the Subject instance).

hope that helps!

Update with some rough sample code...

// this is descriptive way to identify unique error types you may care about
enum ErrorType {
Unauthorized,
ServiceUnavailable,
ServiceError
}

// this class should bundle together enough information about an
// error that has occurred so consumers can decide how to respond
// e.g. is this a "fatal" error, or can the associated operation be
// retried?
class ErrorState {
public final ErrorType type;
public final boolean fatal;

ErrorState(ErrorType type, boolean fatal) {
this.type = type;
this.fatal = fatal;
}
}

// the Interceptor creates new instances of ErrorState and pushes
// them through the Subject to notify downstream subscribers
class ResponseCodeCheckInterceptor implements Interceptor {
private final Subject<ErrorState> errorStateSubject;

ResponseCodeCheckInterceptor(Subject<ErrorState> errorStateSubject) {
this.errorStateSubject = errorStateSubject;
}

@Override
public Response intercept(@NonNull Chain chain) throws IOException {
final Response response = chain.proceed(chain.request());

if(response.code() == HttpStatus.UNAUTHORIZED.value()) {
errorStateSubject.onNext(new ErrorState(ErrorType.Unauthorized, false));
} else if (response.code() == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
errorStateSubject.onNext(new ErrorState(ErrorType.ServiceError, true));
} else if (response.code() == HttpStatus.SERVICE_UNAVAILABLE.value()) {
errorStateSubject.onNext(new ErrorState(ErrorType.ServiceUnavailable, false));
}

return response;
}
}

// the Presenter is created with a reference to the same Subject, but
// retains a reference to it as an Observable. the Consumer instance
// supplied as the onNext() handler is where you'd put your logic to
// handle ErrorStates. also, the Presenter should be lifecycle-aware
// so as to create and dispose of subscriptions at the appropriate
// times.
class Presenter {
private final Observable<ErrorState> errorStateStream;

private Disposable errorStateSubscription;

class Presenter(Observable<ErrorState> errorStateStream) {
this.errorStateStream = errorStateStream
}

public void onStart() {
errorStateSubscription = errorStateStream.subscribe(
next -> {
/* Invoke views/etc */
},
error -> {
/* handle stream error during */
}
);
}

public void onStop() {
if(errorStateSubscription != null) {
errorStateSubscription.dispose();
}
}
}

How to change parameters in retry request after error in RxJava

You're using the wrong operator. retryWhen will retry your original observable if it encounters an error. What you need is onErrorResumeNext. Something like

 holder.getApi(GuideProfileApi.class)
.getProfile(String.valueOf(holder.getServerId()), holder.getServerToken())
.subscribeOn(Schedulers.io())
.onErrorResumeNext(new Function<Throwable, ObservableSource<?>>() {
@Override
public ObservableSource<?> apply(Throwable throwable) {
if (throwable instanceof HttpException && ((HttpException)throwable).code() == 401) {
RegistryLoginResult loginResult = holder.login().blockingSingle();
return holder.getApi(GuideProfileApi.class)
.getProfile(String.valueOf(loginResult.getUserId()), loginResult.getSessionToken());
}
return Observable.error(throwable);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<ProfileResult>() {
@Override
public void accept(ProfileResult profileResult) throws Exception {
Log.d("Result", profileResult.toString());
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
Log.e("Result", throwable.getLocalizedMessage());
}
});

How to retry HTTP requests with OkHttp/Retrofit?

For Retrofit 2.x;

You can use Call.clone() method to clone request and execute it.

For Retrofit 1.x;

You can use Interceptors. Create a custom interceptor

    OkHttpClient client = new OkHttpClient();
client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
client.interceptors().add(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();

// try the request
Response response = chain.proceed(request);

int tryCount = 0;
while (!response.isSuccessful() && tryCount < 3) {

Log.d("intercept", "Request is not successful - " + tryCount);

tryCount++;

// retry the request
response.close()
response = chain.proceed(request);
}

// otherwise just pass the original response on
return response;
}
});

And use it while creating RestAdapter.

new RestAdapter.Builder()
.setEndpoint(API_URL)
.setRequestInterceptor(requestInterceptor)
.setClient(new OkClient(client))
.build()
.create(Adapter.class);

How to parse success body response on http exception 401?

You can use Retrofit's Response class for that purpose, which is a wrapper over your response object, it has both your response's data and error bodies and also the success state, so instead of doing Single<RESPONSE> use Single<Response<RESPONSE>>.

Parsing the response object can be something like this:

{ t: Response<RESPONSE> ->
if (t.isSuccessful())
// That's the usual success scenario
else
// You have a response that has an error body.
}
,
{ t: Throwable ->
// You didn't reach the endpoint somehow, maybe a timeout or an invalid URL.
}

RxJava linear backoff, passing in earlier date parameters on retry

OK, now that I can answer this question again, here we go:

Observable.just("2016-3-10", "2016-3-9", "2016-3-8")
.concatMap(date -> pictureService.getPhotos(date))
.filter(response -> response != null)
.take(1)...

For a detailed explanation take a look at this blog post by Dan Lew: http://blog.danlew.net/2015/06/22/loading-data-from-multiple-sources-with-rxjava/

In short: concat (and concatMap) will subscribe to each new Observable only after the previous one has emitted onCompleted AND only if more items are needed downstream. The take(1) will unsubscribe after the first non-null response and therefore concatMap will just not subscribe to the next Observable.

EDIT: To check that really only one request is sent you could

1.) Enable logging in Retrofit (if you are recent version by adding an Interceptor to the OkHttpClient). This lets you directly observe the requests that are being sent.

2.) Add a doOnSubscribe() like this:

.concatMap(date -> pictureService.getPhotos(date)
.doOnSubscribe(new Action0() {
@Override
public void call() {
Log.d(TAG, "sending request for " + date);
}
});
)

Since Retrofit requests are only sent upon subscription the log message will appear if and only if a request is then sent.



Related Topics



Leave a reply



Submit