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 invokeonNext()
with the next error the application has incurred. in this case the producer would be theResponseCodeCheckInterceptor
instance. rather than throwing the various exceptions, this instance would instead emit anErrorState
describing the error that just occurred. (assumeErrorState
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 anObservable<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
How to Get the Currently Displayed Fragment
How to Figure Out Which Sim Received Sms in Dual Sim Android Device
How to Close the Current Fragment by Using Button Like the Back Button
How to Get Context in Android Mvvm Viewmodel
Touchableopacity Not Working Inside an Absolute Positioned View
Force Override Camera Image on Android
Android Studio - Failed to Apply Plugin [Id 'Com.Android.Application']
How to Set Transparency of a Background Image Android Xml File
How to Check If a Value Exists Already in a Firebase Data Class Android
How to Go Back to Previous Fragment from Activity
Why Livedata Observer Is Being Triggered Twice for a Newly Attached Observer
Android Volley - Basicnetwork.Performrequest: Unexpected Response Code 400
Could Not Resolve All Dependencies for Configuration ':Classpath'
Changing Cardview Shadow Color
Get City Name and Postal Code from Google Place API on Android
Android Button Background Is Taking the Primary Color
React Native: Java_Home Is Not Set and No 'Java' Command Could Be Found in Your Path
How to Show Only Specific Part of Website in Android Webview