I am having an issue using RxJava on Android to read from async I/O. I create a Flowable and subscribe to an emitter. It works the first time, calling onNext and then onComplete when finished, but when I try the same operations again(create a new Flowable and subscribe), I get an UndeliverableException if I intentionally throw an I/O error. If I do an emitter.isCancelled, it returns true. Not sure why this happens.
Flowable getFlowable(){
Flowable.create(new FlowableOnSubscribe<Object>() {
#Override
public void subscribe(FlowableEmitter<Object> emitter) throws Exception {
getIOResponse(new IOListener() {
#Override
public void onInfo(Object ioResponse) {
emitter.onNext(ioResponse);
emitter.onComplete();
}
#Override
public void onError(Exception e) {
emitter.onError(e); //Throws UndeliverableException, emitter already cancelled
}
}
}
}
}
static void subscribe(){
disposable = getFlowable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.timeout(TIME_OUT_SECONDS, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
.subscribe(new Consumer<Object>() {
#Override
public void accept(Object myObject) throws Exception {
someListener.onSuccess(myObject);
}
}, throwable -> {
someListener.onError(throwable);
}, () -> {
Log.d("****", "Completed");
});
}
Related
Currently, I am fetching data from Web API using RxAndroid and Retrofit, and want to store that Data in Room database but getting an exception
As I search and found that, room database operations won't work on UI thread so I added .subscribeOn(Schedulers.io()) in RXAndroid
still it is throwing
java.lang.IllegalStateException: Cannot access the database on the main thread since it may potentially lock the UI for a long period of time.
public void onClickLogin(View view) {
io.reactivex.Observable
.zip(getLogin(Constants.EMAILID, Constants.PASSWORD),
getUserInfo(Constants.EMAILID, Constants.PASSWORD),
getProductDetails(Constants.EMAILID, Constants.PASSWORD).subscribeOn(Schedulers.io()),
.observeOn(AndroidSchedulers.mainThread())
new Function3<List<LoginModule>,
List<UserInfoModule>, ProductModule, AllZipData>() {
#Override
public AllZipData apply(List<LoginModule> loginModuleList, List<UserInfoModule> useerInfoModules, ProductModule productModule) throws Exception {
AllZipData allZipData = new AllZipData();
allZipData.setLoginModuleList(loginModuleList);
allZipData.setUserInfoModuleList(UserInfoModule);
allZipData.setProductModule(productModule);
return allZipData;
}
}).subscribe(new Observer<AllZipData>() {
#Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
#Override
public void onNext(AllZipData allZipData) {
MyDatabase MyDatabase = MyDatabase.getInstance(context);
for (int i = 0; i < allZipData.getUserInfoModuleList().size(); i++) {
UserInfoTable userInfoTable = new UserInfoTable();
userInfoTable.setValue1(allZipData.getUserInfoModuleList().get(i).getValue1());
userDatabase.userDao().insertUserInfo(userInfoTable);
}
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: all zip data " + e.toString());
}
#Override
public void onComplete() {
Log.e(TAG, "onComplete: all data zipped");
}
});
}
how to solve this exception using RxAndroid.
How to add retryWhen();?
Where does this exception happen? If it is in onNext, that's because you specified observeOn(mainThread()) thus the database access happens on the main thread.
Try this
Observable.zip(
getLogin(Constants.EMAILID, Constants.PASSWORD)
.subscribeOn(Schedulers.io()), // <--------------------------------
getUserInfo(Constants.EMAILID, Constants.PASSWORD)
.subscribeOn(Schedulers.io()), // <--------------------------------
getProductDetails(Constants.EMAILID, Constants.PASSWORD)
.subscribeOn(Schedulers.io()) // <--------------------------------
)
.observeOn(Schedulers.io()) // <--------------------------------
.doOnNext(allZipData -> {
MyDatabase MyDatabase = MyDatabase.getInstance(context);
for (int i = 0; i < allZipData.getUserInfoModuleList().size(); i++) {
UserInfoTable userInfoTable = new UserInfoTable();
userInfoTable.setValue1(
allZipData.getUserInfoModuleList().get(i).getValue1()
);
userDatabase.userDao().insertUserInfo(userInfoTable);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<AllZipData>() {
#Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
#Override
public void onNext(AllZipData allZipData) {
// notify UI here?
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: all zip data " + e.toString());
}
#Override
public void onComplete() {
Log.e(TAG, "onComplete: all data zipped");
}
});
I believe this line still needs some RxJava operations :
userDatabase.userDao().insertUserInfo(userInfoTable);
I believe the insertUserInfo in your dao should return a Completable.
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
Clearly says that you're running on MainThread of your application, which leads to freezing of the screen. You should handle your queries or long running operations on background thread of your application.
Change this
observeOn(AndroidSchedulers.mainThread())
to
observeOn(Schedulers.io())
Use Map to perform operation. Check this
.subscribeOn(Schedulers.io())
.map {
}
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
}
.subscribeWith(new DisposableObserver<AllZipData>() {
#Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
#Override
public void onNext(AllZipData allZipData) {
}
#Override
public void onError(Throwable throwable) {
}
})
I'm testing RxJava at the moment, and I get confused.
I placed method with Thread.sleep(5000)
into Schedulers.io()
CompositeDisposable compositeDisposable = new CompositeDisposable();
compositeDisposable.add(longOperation()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableCompletableObserver() {
#Override
public void onComplete() {
customToast("long operation done");
}
#Override
public void onError(Throwable e) {
}
}));
I read, we should move long operation into schedulers.io because we can't freeze UI thread, but in this case, I got freeze UI. What is wrong ?
longOperation() have Thread.sleep(5000) inside.
//after edit Callable
private void doSomething() throws InterruptedException {
CompositeDisposable compositeDisposable = new CompositeDisposable();
compositeDisposable.add(Observable.fromCallable(()-> longOperation())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableCompletableObserver() {
#Override
public void onComplete() {
customToast("long operation done");
}
#Override
public void onError(Throwable e) {
}
}));
}
private Completable longOperation() throws InterruptedException {
Thread.sleep(5000);
return Completable.complete();
}
You should be using defer() so that it actually executes on the scheduler instead of when you're trying to create the completable.
private void doSomething() throws InterruptedException {
CompositeDisposable compositeDisposable = new CompositeDisposable();
compositeDisposable.add(Completable.defer(new Callable<CompletableSource>() {
#Override
public CompletableSource call() throws Exception {
return longOperation();
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableCompletableObserver() {
#Override
public void onComplete() {
customToast("long operation done");
}
#Override
public void onError(Throwable e) {
}
}));
}
private Completable longOperation() throws InterruptedException {
Thread.sleep(5000);
return Completable.complete();
}
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 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());
}
});
As an example to getting started with RxAndroid I'm trying to implement a searchbox which triggers a rest call when the users inserts something.
So far I have two working parts. The first observing the EditTextView ...
RxTextView.textChangeEvents(searchEditText)
.debounce(400, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<TextViewTextChangeEvent>() {
#Override
public void onCompleted() {
Timber.d("onCompleted");
}
#Override
public void onError(Throwable e) {
Timber.e(e, "onError");
}
#Override
public void onNext(TextViewTextChangeEvent e) {
Timber.d("onNext" + e.text().toString());
}
});
... and the second part calling the REST API by using a Retrofit Service:
APIManager.getService().searchRestaurants("test")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Restaurant>>() {
#Override
public void onCompleted() {
Timber.d("onCompleted");
}
#Override
public void onError(Throwable e) {
Timber.e(e, "onError");
}
#Override
public void onNext(List<Restaurant> restaurants) {
Timber.d("onNext");
for (Restaurant restaurant : restaurants) {
Timber.d(restaurant.getId() + ": " + restaurant.getName());
}
}
});
My Problem is combining the two parts. I tried by using the flatMap Operator as following:
RxTextView.textChangeEvents(searchEditText)
.debounce(400, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<TextViewTextChangeEvent, Observable<List<Restaurant>>>() {
#Override
public Observable<List<Restaurant>> call(TextViewTextChangeEvent txtChangeEvt) {
return APIManager.getService().searchRestaurants(txtChangeEvt.text().toString());
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Restaurant>>() {
#Override
public void onCompleted() {
Timber.d("onCompleted");
}
#Override
public void onError(Throwable e) {
Timber.e(e, "onError");
}
#Override
public void onNext(List<Restaurant> restaurants) {
Timber.d("onNext");
for (Restaurant restaurant : restaurants) {
Timber.d(restaurant.getId() + ": " + restaurant.getName());
}
}
});
When I do this I get following exception:
java.lang.IllegalStateException: Must be called from the main thread. Was: Thread[RxCachedThreadScheduler-1,5,main]
at com.jakewharton.rxbinding.internal.Preconditions.checkUiThread(Preconditions.java:28)
at com.jakewharton.rxbinding.widget.TextViewTextChangeEventOnSubscribe.call(TextViewTextChangeEventOnSubscribe.java:21)
at com.jakewharton.rxbinding.widget.TextViewTextChangeEventOnSubscribe.call(TextViewTextChangeEventOnSubscribe.java:12)
So I tried to fix that by calling subscribeOn(AndroidSchedulers.mainThread() but in this case, of course, I get an NetworkOnMainThread Exception.
So how Do I do this?
What is a proper way to combine different Observables which should execute on different Threads?
Just remove the first .observeOn(AndroidSchedulers.mainThread()). Take a look at this example
Observable.just(1) // 1 will be emited in the IO thread pool
.subscribeOn(Schedulers.io())
.flatMap(...) // will be in the IO thread pool
.observeOn(Schedulers.computation())
.flatMap(...) // will be executed in the computation thread pool
.observeOn(AndroidSchedulers.mainThread())
.subscribe(); // will be executed in the Android main thread (if you're running your code on Android)