How to implement check-chain by Rxjava/RxAndroid - android

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(...)

Related

Test until success using rxjava2

What is the correct way to implement the tests below using rxjava2?
Given a list of ntp servers, test each one until you succeed.
Example:
time.nist.gov -> timeout
pool.ntp.org -> timeout
time.google.com -> success, get date
time.apple.com -> ignore
I do not want to test all in parallels but one by one. And if all fail, it restarts the test again.
Using only one server, the code I'm using is this:
public void getTime() {
timeObservable = Observable
.fromCallable(new Callable<Date>() {
#Override
public Date call() throws IOException {
return connectAndGetTime(HOST);
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(new Consumer<Throwable>() {
#Override
public void accept(Throwable error) {
Timber.tag(TAG).e(error);
}
})
.retry(5);
timeObservable.subscribe(new Consumer<Date>() {
#Override
public void accept(Date date) {
mDate = date;
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) {
Timber.tag(TAG).e(throwable);
}
});
}
Thanks!
Thanks Alexei, you're right.
Why complicate things?
The end result looks like this:
public void getTime() {
timeObservable = Observable
.fromCallable(new Callable<Date>() {
#Override
public Date call() {
for (String host : Arrays.asList("time.google.com", "time.apple.com", "time.nist.gov")) {
try {
return connectAndGetTime(host);
} catch (Exception e) {
Timber.tag(TAG).d("Sync (%s) fail!", host);
}
}
return null;
}
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(new Consumer<Throwable>() {
#Override
public void accept(Throwable error) {
Timber.tag(TAG).e(error);
}
})
.retry(5);
timeObservable.subscribe(new Consumer<Date>() {
#Override
public void accept(Date date) {
mDate = date;
}
}, new Consumer<Throwable>() {
#Override
public void accept(Throwable throwable) {
Timber.tag(TAG).e(throwable);
}
});
}

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 do a Throwable call in a flatMap in RxJava2?

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;
}
}
})

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 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