Combine RxTextView Observable and Retrofit Observable - android

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)

Related

Android Room database RxAndroid, Exception : java.lang.IllegalStateException: Cannot access database on the main thread since

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

RxAndroid Emitter disposed without complete/error

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

RxAndroid: onCompleted not being called

I'm using rx libraries im my app to call some REST api on my server and to show the results on screen.
I'm also following the MVP design pattern. So I have a Presenter and an Interactor classes.
In MainInteractor.java I have the following method:
public Observable<Card> fetchCard(final String clientId, final CardFetchedListener listener) {
Log.i(TAG, "FetchCard method");
// Manipulate the observer
return CARDS
.doOnCompleted(new Action0() {
#Override
public void call() {
Log.d(TAG, "CARDS Completed");
}
})
.flatMap(new Func1<Card, Observable<Card>>() {
#Override
public Observable<Card> call(final Card card) {
return ResourceClient.getInstance(card)
.getIDCard()
.observeOn(AndroidSchedulers.mainThread())
.doOnError(new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
Log.w(TAG, "interactor -> fetchCard 2", throwable);
}
}
})
.flatMap(new Func1<CardMeta, Observable<Card>>() {
#Override
public Observable<Card> call(CardMeta cardMeta) {
card.setCardMeta(cardMeta);
saveOrUpdateCardToTheDb(card);
return Observable.just(card);
}
})
.doOnCompleted(new Action0() {
#Override
public void call() {
Log.d(TAG, "Completed body");
}
});
}
});
}
In the logs I can see the "Completed Body" string.
The above method is being called by MainPresenter.java class as follows:
interactor.fetchCard(clientId, this)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Card>() {
#Override
public void onCompleted() {
Log.i(TAG, "fetchCard onCompleted");
view.hideProgressDialog();
view.updateCardsAdapter(cards);
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "Fetch Card error ", e);
onFailure(parseThrowable(e));
}
#Override
public void onNext(Card card) {
if (card != null) {
Log.i(TAG, card.getTenant() + " was fetched and will be displayed");
}
}
});
The problem is that the onCompleted method in the Presenter class is never bein called. I have tried to call onCompleted myself and it worked, but the problem is I don't know actually when the observable has finished emitting cards.
What am I doing wrong here?
UPDATE
CARDS is also an observable that contains meta info. It is initialized using
Observable.from(tenants)
.filter(...).flatMap(// I'm using create operator here and it is calling its onCompleted method successflly);

Calling another observable during onComplete

I have an api call which is a Completable. If Api call is success I have to clear some temp db values (don't do this if api call fails) For clearing temp values from db I have another Completable method. What I have done initially was something like this.
apiCall()
.observeOn(Schedulers.io())
.doOnComplete(() -> clearTempDb())
.subscribeWith(new DisposableCompletableObserver() {
#Override
public void onComplete() {
Log.d(TAG, "onComplete: ");
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getMessage());
}
});
private Completable clearTempDb() {
return Completable.fromAction(() -> {
Log.d(TAG, "clear temp db started");
Thread.sleep(2000);
Log.d(TAG, "clear temp db completed");
}).subscribeOn(Schedulers.io());
}
private Completable apiCall() {
return Completable.fromAction(() -> Thread.sleep(2000)).subscribeOn(Schedulers.io());
}
Then I realized that in doOnComplete calling just clearTempDb() wont work unless you subscribe to it.
So I changed it to
apiCall()
.observeOn(Schedulers.io())
.doOnComplete(() -> clearTempDb().subscribe())
.subscribeWith(new DisposableCompletableObserver() {
#Override
public void onComplete() {
Log.d(TAG, "onComplete: ");
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getMessage());
}
});
That works , but what happens is any error occurred in the clearTempDb would not be a part of the main stream and will not be passed to the actual subscribers onError
Then I made clearTempDb as a part of the main observable chain, like
apiCall().andThen(clearTempDb())
Now it is working as expected. But the issue is apiCall and clearTempDb have become sequential. which means after completing these 2 only onComplete will be called. But I don't want to wait till the clearTempDb to finish, to complete my main observable.
Again I went back to the doOnComplete with error ignored
apiCall()
.doOnComplete(()->clearTempDb().onErrorComplete().subscribe())
.subscribeWith(new DisposableCompletableObserver() {
#Override
public void onComplete() {
Log.d(TAG, "onComplete: ");
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getMessage());
}
});
Now its working fine but I don't know whether it is the proper solution or not. Because in many tutorials I have seen that doing any heavy task in the doOnNext, doOnComplete or such intermediate events are not recommended as it will be outside of your observable chain and its kind of interrupting an active observable chain. So how can I do it in the proper way? Is apiCall().andThen(clearTempDb()) the recommended and only possible way?

RxJava 2, Retrofit 2 + Retrolambda - chaining 2 requests together

The first API call returns a list of elements and I then want to subsequently call another API with a String returned in each element of the list from the first API call. I (think I) have got it so that it's calling the second API call with each element of the list but I am unsure how to then subscribe to that to get the results returned from the second call.
discogsService.getSearchResults(searchTerm, mContext.getString(R.string.token))
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
// Turns the result into individual elements
.flatMapIterable(RootSearchResponse::getSearchResults)
// I believe this then calls .getRelease() with each ID string
.map(result -> discogsService.getRelease(result.getId()));
Retrofit Interface:
public interface DiscogsService
{
#GET("database/search?")
Observable<RootSearchResponse> getSearchResults(#Query("q") String searchTerm, #Query("token") String token);
#GET("releases/")
Observable<Release> getRelease(#Query("release_id") String releaseId);
}
I'm unsure where to go from here.
I believe .subscribe(...) then gives me the ability to get the Observable<Release> returned from each .getRelease(...). As the above method is called in the Model layer I then need to set up a subscriber in this model layer to pass back to the Presenter and then an additional subscriber in the Presenter to deal with each Observable as the Presenter has access to the View.
Is there a way so that I can just return each Observable from the Model layer so I don't need to have two separate .subscribe(...)s? Or should I use two separate .subscribe(...)s as I can then catch errors on the both of them? I only want the results from the second call.
Here is the full code that I have tried:
In Model:
discogsService.getSearchResults(searchTerm, mContext.getString(R.string.token))
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.flatMapIterable(RootSearchResponse::getSearchResults)
.subscribeOn(Schedulers.io())
.map(result -> discogsService.getRelease(result.getId()))
.subscribe(new Observer<Observable<Release>>()
{
#Override
public void onSubscribe(Disposable d)
{
}
#Override
public void onNext(Observable<Release> value)
{
mainPresenter.addToRecyclerView(value);
}
#Override
public void onError(Throwable e)
{
}
#Override
public void onComplete()
{
}
});
In Presenter:
#Override
public void addToRecyclerView(Observable<Release> value)
{
value .observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Observer<Release>()
{
#Override
public void onSubscribe(Disposable d)
{
}
#Override
public void onNext(Release value)
{
Log.e(TAG, "Success! " + value);
results.add(value);
}
#Override
public void onError(Throwable e)
{
Log.e(TAG, "Error: " + e.toString());
Log.e(TAG, "Error: " + e.toString());
}
#Override
public void onComplete()
{
}
});
I would rather expose an Observable<Release> at model level:
Observable<Release> getReleases(...) {
return discogsService.getSearchResults(...)
.flatMapIterable(RootSearchResponse::getSearchResults)
.flatMap(result -> discogsService.getRelease(result.getId()));
}
Presenter would just subscribe to it:
getReleases
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new Observer<Release>()
{
#Override
public void onSubscribe(Disposable d)
{
}
#Override
public void onNext(Release value)
{
Log.e(TAG, "Success! " + value);
results.add(value);
}
#Override
public void onError(Throwable e)
{
Log.e(TAG, "Error: " + e.toString());
Log.e(TAG, "Error: " + e.toString());
}
#Override
public void onComplete()
{
}
});
Only one Observable. Note the switch from map() to flatMap() for the second request in getReleases(...). Behind the scene this is where occur the second subscribe.
The final subscribe will receive errors from both requests. I prefer to let the consumer (Presenter) handle errors, because it's the one who care about the response and know what to do in case of errors (displaying a message for example).
It's the one who 'drive' the Observable, who create, dispose it, so it's also his duty to assign thread imho.
Observable make very good contract to expose from one layer to another. It describe the data type, how to consume it and the pattern (Observable ? Single ? Flowable ?).

Categories

Resources