This question already has answers here:
retrofit with rxjava handling network exceptions globally
(2 answers)
Closed 6 years ago.
I'm new in RxJava, but I like it. And now I have little problem.
I'm using RxJava + Retrofit.
If I have valid token for user, I get response for API, but if token is invalid, I must refresh token and try make request again.
Valid token:
Make reuqest
Get response
Done
Invalid token:
Make request
Get response
If response_code == 403, need refresh token
Refresh token
Here I want go to 1 step and make request to API again
Else - done
How to make it using RxJava?
Try this one has my knowledge
service.normalRequest()
.flatMap( new Func1<Response, Observable<Response>>() {
#Override
public Observable<Response> call(Response response) {
if (response.code() == 403) {
return service.refreshToken(refreshRequest)
.flatMap(new Func1<Response, Observable<Response>>() {
#Override
public Observable<Response> call(Response response) {
return service.normalRequest();
}
});
} else {
return Observable.just(response);
}
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Subscriber<Response>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Response response) {
}
});
There is a special operator that can be used in this case:
public final Observable<T> retryWhen(final Func1<? super Observable<? extends Throwable>, ? extends Observable<?>> notificationHandler)
If you implement it you will see what parameters it accepts:
observable.retryWhen(new Func1<Observable<? extends Throwable>, Observable<T>>() {
#Override
public Observable<T> call(final Observable<? extends Throwable> error) {
}
});
You can see, that you get the error that was thrown by OkHttp. It is always throwing HttpException, if you call:
httpException.code()
You will get HTTP code number.
So the implementation of the function above might look like:
observable.retryWhen(new Func1<Observable<? extends Throwable>, Observable<T>>() {
#Override
public Observable<T> call(final Observable<? extends Throwable> error) {
return error.flatMap(doRelogging());
}
});
I have made a small library, that does what you expect:
RetrofitRxErrorHandler
You might try it or just look into sources for more advanced retry strategies regarding fetching API errors.
Related
Is there anyway to retry a retrofit http request when network connection available with Rx-java?
This is my request method
public DisposableObserver<List<Photo>> getNewPhotos(final int page,
int perPage,
String orderBy,
ObservableTransformer<List<Photo>, List<Photo>> observableTransformer,
final Callback<List<Photo>> callBack) {
return photoService.getPhotos(page, perPage, orderBy)
.compose(observableTransformer)
.retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {
return null;
}
})
.onErrorResumeNext(new Function<Throwable, ObservableSource<? extends List<Photo>>>() {
#Override
public ObservableSource<? extends List<Photo>> apply(Throwable throwable) throws Exception {
return Observable.error(throwable);
}
})
.subscribeWith(new DisposableObserver<List<Photo>>() {
#Override
public void onNext(List<Photo> value) {
callBack.onSuccess(value);
}
#Override
public void onError(Throwable e) {
callBack.onError(new NetworkError(e));
}
#Override
public void onComplete() {
}
});
}
i think maybe i could do something in retryWhen() method.
i want retrofit to trigger for internet connection and retry the last request when the connection is back.
i know the traditional way to retry but i think there must be a method or something in Rx-java to handle this.
if someone knows its good to share it with me.
I'm using Retofit2 and RxJava2 to fetch data from API. I am using the following code
Observable<Recipe> recipeObservable = getDataManager().getRecipes(String.valueOf(page));
recipeObservable.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Recipe>() {
#Override
public void onSubscribe(#NonNull Disposable d) {
}
#Override
public void onNext(#NonNull Recipe recipe) {
getMvpView().updateRecipeList(recipe.getResults());
++page;
//getMvpView().hideLoading();
}
#Override
public void onError(#NonNull Throwable e) {
if (e instanceof HttpException)
Timber.d("Network Error");
}
#Override
public void onComplete() {
}
});
But the problem is Timber.d("Network Error"); never gets executed. I looked on various websites but could not solve the problem. I'm use to Retrofit2 but not fimiliar with RxJava2. So Please help. If more information is required please ask, I'll post it.
HttpException is not related to network failures. IOException is what you are looking for.
I'm trying to make a request using RxJava and Retrofit(2.3). I'm expecting that in case of any error I can retry or show a message to the client.
However, I notice that sometimes I have a SocketException which results in not calling onError, apparently the subscriber of the request just unsubscribes without calling anything else (not onComplete neither onError). Anyone knows why this is happening and how can I solve this in a generic way (without simply doing onUnsubscribe() and checking if the observable did not send any onError or onComplete)?
On my interface I have something like this:
#GET("userInfo")
Observable<List<UserInfo>> getUserInfo(#Header("token") String token);
This is how I create my observable:
public Observable<UserModel> requestUserInfo(final String token) {
return mService.getUserInfo(token)
.retryWhen(new RetryWithDelay(HTTP_RETRIES), HTTP_TIME_BETWEEN_RETRIES)))
.flatMap(new Func1<List<UserInfo>, Observable<UserModel>() {
#Override
public Observable<UserModel> call(List<UserInfo> userInfo) {
return Observable.just(new UserModel(userInfo));
}
});
}
------ UPDATE -------
This is how I call the requestUserInfo method on my presenter
private CompositeSubscription mCompositeSubscription = null;
public PresenterX(ViewX view) {
...
mCompositeSubscription = new CompositeSubscription();
}
public void getUserModel() {
String userToken = new AccessModel().getUserToken();
mCompositeSubscription.add(mNetworkRequestModel.requestUserInfo(userToken)
.flatMap(new Func1<UserModel, Observable<UserModel>>() {
#Override
public Observable<UserModel> call(UserModel userModel) {
if (userModel != null) {
saveUserModel(userModel); //sync saving
return Observable.just(userModel);
} else {
return Observable.error(new SaveException());
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<UserModel>() {
#Override
public void onCompleted() {
Log.i(TAG, "Subscriber was completed")
}
#Override
public void onError(Throwable e) {
Log.i(TAG, "Subscriber called onError")
mView.handleErrors(e);
}
#Override
public void onNext(UserModel userModel) {
Log.i(TAG, "Subscriber called onNext")
mView.populateUserInfo(userModel);
}
}));
}
//called by activity when onDestroyMethod is called
//I assume this is not called as I have other requests running parallelly to this getUserModel() and they are not terminated, despite having other compositeSubscription to manage those
public void onDestroy(){
mCompositeSubscription.clear();
}
As I have a HttpLoggingInterceptor, this is the only log printed to me while the request suddenly stops.
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
#Override
public void log(String message) {
LOG.info(Thread.currentThread(), String.format("%s", message));
}
});
Here is the log exception:
I/ExampleApp-ApiClient(21338): : Thread: 1343 | <-- HTTP FAILED: java.net.SocketException: Socket closed
Because you try to get rx object (Observable) from server. You have incorrect function
Remove Observable
#GET("userInfo")
List<UserInfo> getUserInfo(#Header("token") String token);
I am not able to get success response status code from response like 200,201.. etc. As we can easily get error codes from RetrofitError class like error.isNetworkError() and error.getResponse().getStatus(). Is there any workaround for getting status codes?
As per Retrofit 2.0.2, the call is now
#Override
public void onResponse(Call<YourModel> call, Response<YourModel> response) {
if (response.code() == 200) {
// Do awesome stuff
} else {
// Handle other response codes
}
}
Hope it helps someone :-)
EDIT: Many apps could also benefit from just checking for success (response code 200-300) in one clause, and then handling errors in other clauses, as 201 (Created) and 202 (Accepted) would probably lead to the same app logic as 200 in most cases.
#Override
public void onResponse(Call<YourModel> call, Response<YourModel> response) {
if (response.isSuccessful()) {
// Do awesome stuff
} else if (response.code() == 401) {
// Handle unauthorized
} else {
// Handle other responses
}
}
You can get the status code in success() just like you do it in failure()
#Override
public void success(Object object, Response response) {
response.getStatus() // returns status code integer
}
Since you have a Response object in success callback as response.getStatus()
EDIT
I assume you are using okhttp with retrofit.
okhttp has a powerful tool called Interceptor
You can catch the response before retrofits Callback and get status code from the response:
OkHttpClient client = new OkHttpClient();
client.interceptors().add(new Interceptor(){
#Override
public Response intercept(Chain chain) throws IOException{
Request request = chain.request();
Response response = chain.proceed(request);
response.code()//status code
return response;
});
// then add it to you Restclient like this:
restAdapter = new RestAdapter.Builder()
.setEndpoint(URL_SERVER_ROOT)
.setClient(new OkClient(client)) //plus your configurations
.build();
To learn more about interceptors visit here.
i achieved it by following codes:
public abstract class BaseCallBack<T> {
public abstract void onSuccess(Response<T> response);
public abstract void onFailure(Response<T> response);
}
public abstract class SuccessCallback<T> extends BaseCallBack<T> implements Callback<T>{
#Override
public void onResponse(Call<T> call, Response<T> response) {
if(response.code()>= 400 && response.code() < 599){
onFailure(response);
}
else {
onSuccess(response);
}
}
#Override
public void onFailure(Call<T> call, Throwable t){
}
#Override
public void onSuccess(Response<T> response) {
}
#Override
public void onFailure(Response<T> response) {
}
}
When the rest api returns 400 etc then the onFailure method will be call by default. If you wanna do something onsuccess:(when returns 200 etc)
ServiceConnector.api.getHomePage().enqueue(new SuccessCallback<Void>() {
#Override
public void onSuccess(Response<Void> response) {
super.onSuccess(response);
}
});
you should get exception code iin onError response
HttpException httpException =((HttpException) e).code();
I'm really new in RxJava, but I'm trying to implement an API using Retrofit framework and RxJava.
On a server side there is an authorization service which handles user's session and in case of some delay in user's actions server breaks his session. After that user has to login again in order to perform new API call. Bad thing is - server always returns HTTP code 200 and for notification about expiration uses some custom JSON response with expiration code, so RxJava doesn't fire Exception during onNext operation because RxJava considers that request was passed successfully.
And the question is: How to implement correct flow to handle custom API exceptions like expiration and retry failed request after some other request (in my case relogin)?
Something like this:
app -> login()
server -> { code:0, ... }
app -> getUsers()
server -> { code:0, ... }
------- in 30 minutes -------
app -> getPicture()
server -> { code:99, ... } // session expired, user unauthorized
app -> login()
server -> { code:0, ... }
app -> getPicture()
server -> { code:0, ... }
I was thinking about something like this, but with no success:
Observable.create(new Observable.OnSubscribe<BackendResponse<String>>() {
#Override
public void call(Subscriber<? super Response<String>> subscriber) {
try {
Response<String> response;
subscriber.onNext(response = getInterface().getUsers());
if (response != null) {
response.checkData(); // throws ServerException in case of code != 0
}
subscriber.onCompleted();
} catch (Exception e) {
subscriber.onError(e);
}
}
}).subscribeOn(Schedulers.io()).retryWhen(new RetryWithSessionRefresh(new SessionService())).subscribe();
and RetryWithSessionRefresh is:
public class RetryWithSessionRefresh implements
Func1<Observable<? extends Notification<?>>, Observable<?>> {
private final SessionService sessionSerivce;
public RetryWithSessionRefresh(SessionService sessionSerivce) {
this.sessionSerivce = sessionSerivce;
}
#Override
public Observable<?> call(Observable<? extends Notification<?>> attempts) {
return attempts
.flatMap(new Func1<Notification<?>, Observable<?>>() {
#Override
public Observable<?> call(final Notification notification) {
final Throwable throwable = notification.getThrowable();
if (throwable instanceof ServerException) {
final ServerException backendException = (ServerException) throwable;
if (backendException.getBackendErrorCode() == Response.AUTHORIZATION_FAILED) {
return sessionSerivce
.observeSessionToken()
.doOnNext(new Action1<TokenCallback>() {
#Override
public void call(TokenCallback token) {
if (token != null) {
DataHolder.getInstance().setAuthToken(token.getToken());
}
}
})
.doOnError(new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
DataHolder.getInstance().setAuthToken("");
}
});
}
}
return Observable.error(notification.getThrowable());
}
});
}
Maybe you van flapMap your response, and return your observable in case of success with Observable.just(your item) or an error when the response isn't valid with Observable.error(your error)