How to do a Throwable call in a flatMap in RxJava2? - android

I'm working with RxJava2 and I was doing simple request, I should do it like the next example:
getCompositeDisposable().add(subscriptionManager.getSubscriptions(getUserAuth().getToken()).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<HttpCustomRes<List<GetSubscriptions>>>() {
#Override
public void accept(#NonNull HttpCustomRes<List<GetSubscriptions>> listHttpCustomRes) throws Exception {
getMvpView().hideLoading();
ErrorCode errorCaught = bypassForErrors(listHttpCustomRes.getError());
if(errorCaught.equals(ErrorCode.SUCCESSFUL_REPSONSE))
getMvpView().showSubscriptions(listHttpCustomRes.getData());
else if(errorCaught.equals(ErrorCode.INVALID_TOKEN) || errorCaught.equals(ErrorCode.NULL_TOKEN))
getMvpView().showLogin();
else
getMvpView().showErrorDialog();
}
}, new Consumer<Throwable>() {
#Override
public void accept(#NonNull Throwable throwable) throws Exception {
getMvpView().hideLoading();
getMvpView().showErrorDialog();
}
}));
Now, I need to do two sequential request, so I'm using the flatMap to do this. In the simple request I have the second call to the method new Consumer to catch errors, but with the flatMap I don't know how to do this. I post the code in the next lines.
getCompositeDisposable().add(accountUserManager.getUserData(getUserAuth().getToken()).flatMap(new Function<UserData, Flowable<HttpCustomRes<List<GetSubscriptions>>>>() {
#Override
public Flowable<HttpCustomRes<List<GetSubscriptions>>> apply(#NonNull UserData userData) throws Exception {
setUserData(userData);
return subscriptionManager.getSubscriptions(getUserAuth().getToken());
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<HttpCustomRes<List<GetSubscriptions>>>() {
#Override
public void accept(#NonNull HttpCustomRes<List<GetSubscriptions>> listHttpCustomRes) throws Exception {
getMvpView().hideLoading();
ErrorCode errorCaught = bypassForErrors(listHttpCustomRes.getError());
if(errorCaught.equals(ErrorCode.SUCCESSFUL_REPSONSE))
getMvpView().showSubscriptions(listHttpCustomRes.getData());
else if(errorCaught.equals(ErrorCode.INVALID_TOKEN) || errorCaught.equals(ErrorCode.NULL_TOKEN))
getMvpView().showLogin();
else
getMvpView().showErrorDialog();
}
}, new Consumer<Throwable>() {
#Override
public void accept(#NonNull Throwable throwable) throws Exception {
getMvpView().hideLoading();
getMvpView().showErrorDialog();
}
})
);
As you can see, I have a throwable in the second method, but not in the first, so if the first crash, the app will crash too. How I can implement the throwable to the first request?
Thank you.

All unchecked exception (well, almost all) will be delivered to the onError handler in the subscriber which is the second Consumer in your subscribe method.
Thus in your case, both exception in the source getUserData Observable and the flatMap mapping function will be handled by the stream and will be delivered to the onError handler.

new Consumer<Throwable>() {
#Override
public void accept(#NonNull Throwable throwable) throws Exception {
try{
getMvpView().hideLoading();
getMvpView().showErrorDialog();
}catch(Exception e){
throw e;
}
}
})

Related

Retrofit 2 and rxJava with Flowable concat handle several response

I'm using retrofit, rxJava and realm I'm using flowable concat for launch a request to the server and another one for realm
This is how I'm creating the disposable :
disposable.add(launchRequest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Config>() {
#Override
public void accept(Config config) throws Exception {
ProfileManager.getInstance().setConfig(config);
}
}, new Consumer<Throwable>() {
#Override
public void accept(#NonNull Throwable throwable) throws Exception {
if (!throwable.getMessage().equals(ErrorUtils.ERROR_CODE_304)) {
throwable.printStackTrace();
}
}
}));
launchRequest function create the Flowable :
Flowable.concat(cloudDataStore.getById(single, diskDataStore, repositoryItem).toFlowable(), diskDataStore.getById(id, repositoryItem).toFlowable())
.map(new Function<Object, T>() {
#Override
public T apply(Object o) throws Exception {
return repositoryItem.toView((T) o);
}
});
cloudDataStore param is the retrofit part and diskDataStore is the realm part.
Everything workings fine here, my trouble is when I handle the retrofit request :
return single.flatMap(new Function<Response<T1>, SingleSource<T1>>() {
#Override
public SingleSource<T1> apply(#NonNull Response<T1> response) throws Exception {
if (response.code() == Integer.valueOf(ErrorUtils.CODE_200)) {
return Single.just(response.body());
} else if (response.code() == Integer.valueOf(ErrorUtils.ERROR_CODE_304)) {
return Single.just(response.body());
} else {
return Single.error(new Throwable(String.valueOf(response.code())));
}
}
});
If the request is sucessfull (status 200) I return the server response to my disposable.
If I got a code 304 the response body is null so the disposable throwable is trigger, but if the throwable is trigger the disposable don't wait the realm response from concat and stop listening.
The workaround I find is to create a empty Object and return it like this :
if (response.code() == Integer.valueOf(ErrorUtils.ERROR_CODE_304)) {
return Single.just(new Config());
}
This trigger the disposable consumer with an empty object and I can after get the realm result with the good value because the throwable is not trigger.
But I don't want to receive this empty result I can do nothing with it and I need for all request to check if the content is not null like this :
.subscribe(new Consumer<Config>() {
#Override
public void accept(Config config) throws Exception {
if (config.getContent != null){
ProfileManager.getInstance().setConfig(config);
}
}
}
How can I return something with Single that don't trigger the consumer and the throwable ?
Thanks to EpicPandaForce the solution was to change the Single into Maybe like this :
.flatMap(new Function<Response<T>, Maybe<T>>() {
#Override
public Maybe<T> apply(#NonNull Response<T> response) throws Exception {
if (response.code() == Integer.valueOf(ErrorUtils.CODE_200)) {
return Maybe.just(response.body());
} else if (response.code() == Integer.valueOf(ErrorUtils.ERROR_CODE_304)) {
return Maybe.empty();
} else {
return Maybe.error(new Throwable(String.valueOf(response.code())));
}
}
});

I use rxjava2+retrofit2+okhttp3 in my android application and get crashed when app not connected to internet

I've find some solution (OnErrorNotImplementedException when Interceptor throws.), but I still get OnErrorNotImplementedException and SocketTimeoutException and the app crashes.
My code to handle HTTP result:
observable.compose(TransformerHelper.<Response<T>>io_main()).doOnSubscribe(new Consumer<Disposable>() {
#Override
public void accept(#io.reactivex.annotations.NonNull Disposable disposable) throws Exception {
requestCallback.onBefore();
}
}).doOnError(new Consumer<Throwable>() {
#Override
public void accept(#io.reactivex.annotations.NonNull Throwable throwable) throws Exception {
requestCallback.onAfter();
requestCallback.onError(throwable);
}
}).doOnComplete(new Action() {
#Override
public void run() throws Exception {
requestCallback.onAfter();
}
}).onErrorReturn(new Function<Throwable, Response<T>>() {
#Override
public Response<T> apply(#io.reactivex.annotations.NonNull Throwable throwable) throws Exception {
return null;
}
}).subscribe(new Consumer<Response<T>>() {
#Override
public void accept(#io.reactivex.annotations.NonNull Response<T> response) throws Exception {
try {
requestCallback.onAfter();
if (response.isSuccess()) {
requestCallback.onSuccess(response.data, response.msg);
} else {
requestCallback.onBizErr(response.code, response.msg);
}
} catch (Exception e) {
requestCallback.onAfter();
requestCallback.onError(e.getCause());
}
}
}, new Consumer<Throwable>() {
#Override
public void accept(#io.reactivex.annotations.NonNull Throwable throwable) throws Exception {
requestCallback.onAfter();
requestCallback.onError(throwable);
}
});
return null; - In RxJava2 null can no longer be emitted to the stream (What's Different in 2.0). This means that returning null will just cause an NPE to be emitted instead.
To put it simply, you will either need to define a valid replacement for a failed request or define a way to deal with the exception. Otherwise it will be handled by the default exception handler which crashes the application.

How to handle exception when is throws in FlowableOnSubscribe?

Flowable.create(new FlowableOnSubscribe<Object>() {
#Override
public void subscribe(#io.reactivex.annotations.NonNull FlowableEmitter<Object> e) throws Exception {
//throws exceptions, how can I handle error it using RxJava
}},BackpressureStrategy.LATEST);
Depends on situation it could cause issues, how could be it handled, I don't want to use if condition.
You need to define the onError in your subscriber:
.subscribe(new Consumer<String>() {
#Override
public void accept(#NonNull String o) throws Exception {
Log.d("test", o);
}
}, new Consumer<Throwable>() {
#Override
public void accept(#NonNull Throwable throwable) throws Exception {
Log.e("test", "throwable: " + throwable.getMessage());
}
});
And then the throwable will be handled by the subscriber.

How to implement check-chain by Rxjava/RxAndroid

i am a newer on RxJava/RxAndroid. I want to use RxJava/RxAndroid to implement the following case: First, get data from network then do some checks on the data, if any of check fails, just show error in Main Thread.
you can see flow chart here!
I try some RxJava operations but fail to find a nice way to do so.
Can someone help me on this? Many thanks!
And I write some test code about this case (using String as data), is there any more simple way?
Observable.just(s)
.flatMap(new Function<String, ObservableSource<String>>() {
#Override
public ObservableSource<String> apply(final String s) throws Exception {
return Observable.create(new ObservableOnSubscribe<String>() {
#Override
public void subscribe(ObservableEmitter<String> e) throws Exception {
if(s.length() < 3){
e.onError(new Throwable("len"));
}else{
e.onNext(s);
e.onComplete();
}
}
});
}
}).flatMap(new Function<String, ObservableSource<String>>() {
#Override
public ObservableSource<String> apply(final String s) throws Exception {
return Observable.create(new ObservableOnSubscribe<String>() {
#Override
public void subscribe(ObservableEmitter<String> e) throws Exception {
if(s.startsWith("a")){
e.onError(new Throwable("start"));
}else{
e.onNext(s);
e.onComplete();
}
}
});
}
}).subscribeOn(AndroidSchedulers.mainThread())
.doOnError(new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) throws Exception {
System.out.println("get error: " + throwable.getMessage());
}
}).subscribe(new Consumer<String>() {
#Override
public void accept(String s) throws Exception {
System.out.println(s);
}
});
While you can do it with flatMap() it is not needed here, you can simply use map() for checking the data and throwing errors:
Observable.just(s)
.map(new Function<String, String>() {
#Override
public String apply(#NonNull String s) throws Exception {
if (s.length() < 3) {
throw new Exception("len");
} else if (s.startsWith("a")) {
throw new Exception("start");
}
return s;
}
}
)
.subscribe(new Consumer<String>() {
#Override
public void accept(#NonNull String s) throws Exception {
System.out.println(s);
}
}, new Consumer<Throwable>() {
#Override
public void accept(#NonNull Throwable throwable) throws Exception {
System.out.println("get error: " + throwable.getMessage();
}
});
here you checking the value emitted and simply throw the appropriate Exception according to your checks.
Anyhow, in your example, you don't need to create by yourself an Observable for emitting errors/passing thru, you can use Observable.error() and Observable.just():
.flatMap(new Function<String, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(#NonNull String s) throws Exception {
if (s.length() < 3) {
return Observable.error(new Exception("len"));
} else if (s.startsWith("a")) {
return Observable.error(new Exception("start"));
} else {
return Observable.just(s);
}
}
})
moreover, your'e not handling onError() at your subscriber (but on doOnError()) so you'll crash with OnErrorNotImplementedException.
You can simplify your code a bit by eliminating Observable.create() (which you should not be using anyway):
Observable.just(s)
.flatMap(new Function<String, ObservableSource<String>>() {
#Override
public ObservableSource<String> apply(final String s) throws Exception {
return s.length() < 3 ? Observable.error(new Throwable("len"))
: Observable.just(s);
}
}).flatMap(new Function<String, ObservableSource<String>>() {
#Override
public ObservableSource<String> apply(final String s) throws Exception {
return s.startsWith("a") ? Observable.error(new Throwable("start"))
: Observable.just(s);
}
})
.subscribe(...)
Or you can use doOnEach and Guava Preconditions:
Observable.just(s)
.doOnEach(s -> {
Preconditions.checkArgument(s >= 3, "len");
Preconditions.checkArgument(!s.startsWith("a"), "start");
})
.subscribe(...)

How to change parameters in retry request after error in RxJava

I send a login request to server with retrofit 2.0, and server return to the client session token, wich I must use in other requests, but this token has limited life-time, and when it is expire server returns HTTP error 401.
I try make re-logon, after getting this error, with help a next code:
holder.getApi(GuideProfileApi.class)
.getProfile(String.valueOf(holder.getServerId()), holder.getServerToken())
.subscribeOn(Schedulers.io())
.retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {
return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(Throwable throwable) throws Exception {
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());
}
});
And retry request is sent, but parameters of request are same as in incorrect request (before re-login). How I can change parameters of the request before sending it again?
You can use retryWhen, but the problem is that your retryWhen retry the same observable object that you create in lazy moment.
Your solution here is use the operator defer to get the host(), since defer it´s not creating the observable when you define it but when it´s consumed by the subscribed.
Observable.defer(()-> holder.getApi(GuideProfileApi.class)
.getProfile(String.valueOf(holder.getServerId()),holder.getServerToken()))
.subscribeOn(Schedulers.io())
.retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {
return throwableObservable.flatMap(new Function<Throwable, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(Throwable throwable) throws Exception {
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());
}
});
You can see some examples of retry here https://github.com/politrons/reactive/blob/master/src/test/java/rx/observables/errors/ObservableExceptions.java
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());
}
});

Categories

Resources