Concatenating observables is not acting as required - RxAndroid - android

I have 2 Observables that do 2 different jobs that returns their observables
First one : SyncDoctors for getting doctor list from my WebService
public Observable<List<Doctor>> SyncDoctors(){
Observable<List<Doctor>> observable = MyWebService.getInterface().GetAllDoctors();
observable.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.computation())
.subscribe(new Subscriber<List<Doctor>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Doctor> doctors) {
if(doctors.size() == 0){
logger.debug("No Coming Doctors");
return;
}
DoctorDao doctorDao = MyApplication.getDaoSession().getDoctorDao();
doctorDao.deleteAll();
doctorDao.insertInTx(doctors);
logger.debug("Doctors are synced successfully to the database");
logger.info(doctors.size()+" doctors have been added to database");
}
});
return observable;
}
Second Observable for getting patients list from my webservice
public Observable<List<Patients>> SyncPatients(){
Observable<List<Patients>> observable = MyWebService.getInterface().GetAllPatients();
observable.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.computation())
.subscribe(new Subscriber<List<Patients>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Patients> patients) {
if(patients.size() == 0){
logger.debug("No Coming Patients");
return;
}
PatientDao PatientDao = MyApplication.getDaoSession().getPatientDao();
patientDao.deleteAll();
PatientDao.insertInTx(Patients);
logger.debug("Patients are synced successfully to the database");
logger.info(Patients.size()+" Patients have been added to database");
}
});
return observable;
}
Now i want to sync both doctors and patients lists and after both syncs finish, i want to show it on the screen of the tablet:
I have function called SyncAll
public void SyncAll(){
Observable<List<Doctor>> doctorsObservable = SyncDoctors();
Observable<List<Patient>> patientsObservable = SyncPatients();
Observable.concat(doctorsObservable, patientsObservable)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.computation())
.subscribe(new Subscriber<Object>() {
#Override
public void onCompleted() {
// Here the code to show on ListView
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Object o) {
logger.debug("On SyncAll Next!!!");
}
});
}
onNext functions I save the list of doctors and list of patients to the database.
now when I call SyncDoctors() alone, it works
also when I call SyncPatients() alone, it works as well.
when I call SyncAll() the Doctors and Patients are not being saved to the database.
The Question is why the SyncDoctors() and SyncPatients() observables' onNext functions are called when I call SyncAll() !!

It is because you activate chain by calling .subscribe() in
Observable<List<Doctor>> doctorsObservable = SyncDoctors();
Observable<List<Patient>> patientsObservable = SyncPatients();
You first create observable, then subscribe to it of SyncDoctors() and SyncPatients();
After that you return this Observable, but web response is triggered upon observable creation.
To solve that use .map():
public Observable<List<Doctor>> SyncDoctors(){
final Observable<List<Doctor>> observable = MyWebService.getInterface().GetAllDoctors();
observable.observeOn(Schedulers.io())
// in your code you performed db io on main thread, here it is fixed
.subscribeOn(Schedulers.io())
.map(new Func1<List<Doctor>, List<Doctor>>() {
#Override
public List<Doctor> call(List<Doctor> doctors) {
if(doctors.size() == 0){
logger.debug("No Coming Doctors");
return;
}
DoctorDao doctorDao = MyApplication.getDaoSession().getDoctorDao();
doctorDao.deleteAll();
doctorDao.insertInTx(doctors);
logger.debug("Doctors are synced successfully to the database");
logger.info(doctors.size()+" doctors have been added to database");
return doctors;
}
})
.observeOn(AndroidSchedulers.mainThread());
// Notice: use Observable.defer() or you'll get the same result all the tim
return Observable.defer(new Func0<Observable<List<Doctor>>>() {
#Override
public Observable<List<Doctor>> call() {
return observable;
}
});
}
You should not use .concat(), because it executes chain elements consequently. use .zip().first() intead.
There is also one issue: you perform db operations on main thread.
move chain to main thread after db update
Version with .zip:
void syncAll(){
Observable<List<Doctor>> doctorsObservable = SyncDoctors();
Observable<List<Patient>> patientsObservable = SyncPatients();
Observable.zip(doctorsObservable, patientsObservable, new Func2<List<Doctor>, List<Patient>, Boolean>() {
#Override
public Boolean call(List<Doctor> doctors, List<Patient> patients) {
return true;
}
})
.first()
.subscribe(new Action1<Boolean>() {
#Override
public void call(Boolean aBoolean) {
logger.debug("On SyncAll Next!!!");
}
});
}

Related

Tolist is not emitting in rxjava

I trying to filter the list after taking the input from edittext ,Actually its filtering the list but i want to group the result to list.
So I used to list operator with SingleObserver but result is not coming; why is this?
RxTextView.textChangeEvents(searchEdit)
.skip(1)
.debounce(400, TimeUnit.MILLISECONDS)
.switchMap(new Function<TextViewTextChangeEvent, Observable<List<VehicleMakeModel>>>() {
#Override
public Observable<List<VehicleMakeModel>> apply(TextViewTextChangeEvent textViewTextChangeEvent) throws Exception {
return Observable.just(variantlist);
}
})
.flatMapIterable(new Function<List<VehicleMakeModel>, List<VehicleMakeModel>>() {
#Override
public List<VehicleMakeModel> apply(List<VehicleMakeModel> v) {
return v;
}
})
.filter(new Predicate<VehicleMakeModel>() {
#Override
public boolean test(VehicleMakeModel v) {
if (searchEdit.getText().toString().isEmpty())
return true;
else
return v.getVariant().toLowerCase().trim().contains(searchEdit.getText().toString().toLowerCase().trim());
}
})
.map(new Function<VehicleMakeModel, VehicleMakeModel>() {
#Override
public VehicleMakeModel apply(VehicleMakeModel integer) throws Exception {
return integer;
}
})
.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<List<VehicleMakeModel>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(List<VehicleMakeModel> vehicleMakeModels) {
System.out.println("testing");
}
#Override
public void onError(Throwable e) {
}
});
There is a must-remember fact about RxBinding: event observables never complete.
RxTextView.textChangeEvents(searchEdit) will never call the complete callback, so toList() won't never return your expected list.
This is not a bug, but a wanted behavior for RxBinding because your textChanges observable will never stop listening for that event.

How to throw error from completable

I am a beginner for rxjava,room and mvvm architecture.I am trying to fetch a user from room database, using Rxjava.
when I get user successfully I want to show a toast and start another activity. and in case of failure, I will show an error message in text input layout.
I have try to do that using following code.
in my Activity, I have a method authorizeUser() which is called on button click.
private void authorizeUser() {
loginViewModel.checkInDb()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onComplete() {
Log.e("Action", "Complete");
showToast();
startAnotherActivity();
}
#Override
public void onError(Throwable e) {
Log.e("Action", "error");
showError();
}
});
}
In my ViewModel
public Completable checkInDb() {
Completable completable= Completable.fromAction(() ->
userDataSource.getSingleRecordFromName(name.get(), password.get())
.subscribe(new SingleObserver<User>() {
#Override
public void onSubscribe(Disposable d) {
isLoading.set(true);
}
#Override
public void onSuccess(User user) {
preference.save(Constants.CURRENT_USER, user)
.subscribe();
isLoading.set(false);
isComplete=true;
Log.e("got","success");
}
#Override
public void onError(Throwable e) {
isLoading.set(false);
passwordError.postValue(new Error("Username or password is incorrect"));
Log.e("got","failure");
isComplete=false;
}
}));
return completable;
}
in UserDataSource class
public Single<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
and in DaoAccess class
public Single<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
Using above code, onComplete method is always called in activity, weather in view model, user fetched successfully or not
But what I want to do is, I want to throw an error or send a notification to the activity when there is error thrown (or on Error method is called in viewmodel). so that I can display error on my activity.
May be my question can be silly, but I am new to this. Please help me.
Although it would be better to maybe wrap the response in LiveData so you would get the subscription/lifecycle "for free", unless there is a specific need to have a completable on the UI, which I don't really any reason for it).
I would instead change your Dao to return Observable instead of Single, and then (one again this is not the optimal solution, should wrap into LiveData) you can return that Observable to the UI:
public Observable checkInDb() {
return userDataSource.getSingleRecordFromName(name.get(), password.get())
.subscribe(new SingleObserver<User>() {
#Override
public void onSubscribe(Disposable d) {
isLoading.set(true);
}
#Override
public void onSuccess(User user) {
preference.save(Constants.CURRENT_USER, user)
.subscribe();
isLoading.set(false);
isComplete=true;
Log.e("got","success");
}
#Override
public void onError(Throwable e) {
isLoading.set(false);
passwordError.postValue(new Error("Username or password is incorrect"));
Log.e("got","failure");
isComplete=false;
}
}));
}
And change your Daos
in UserDataSource class
public Observable<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
and in DaoAccess class
public Observable<User> getSingleRecordFromName(String strName) {
return daoAccess.getSingleRecord(strName);
}
So now your view can checkInDb() and handle these cases.
PS: I'm assuming this daoAccess is not an API call rather a local DB (probably Room).

Using RxAndroid ,after receiving data from network where (in which method ) I should call the data insertion part in DB?

I receive data from network ,I implemented that part, but after receiving data from server in other thread, I want to save the data in Data base,which I want to implement not in main thread,so after receiving data where I should call the DB insertion method.Here is my code
Observable<List<PhotoAlbum>> searchResponseObservable =
mService.getAPI().getAlbums().subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
searchResponseObservable.subscribe(new Observer<List<PhotoAlbum>>() {
#Override
public void onCompleted() {
Log.i("test","onComplete");
}
#Override
public void onError(Throwable e) {
Log.i("test","onComplete");
}
#Override
public void onNext(List<PhotoAlbum> photoAlbums) {
view().showSearchResult(photoAlbums);
}
});
You can use doOnNext() to insert received data into your DB like this:
Observable<List<PhotoAlbum>> searchResponseObservable = mService.getAPI()
.getAlbums()
.subscribeOn(Schedulers.newThread())
.doOnNext(photoAlbums -> insertIntoDb(photoAlbums))
.observeOn(AndroidSchedulers.mainThread());
searchResponseObservable.subscribe(new Observer<List<PhotoAlbum>>() {
#Override
public void onCompleted() {
Log.i("test","onComplete");
}
#Override
public void onError(Throwable e) {
Log.i("test","onComplete");
}
#Override
public void onNext(List<PhotoAlbum> photoAlbums) {
view().showSearchResult(photoAlbums);
}
});
Use flatMap to save in database and use Schedulers IO (Schedulers.io()) for background process
mService.getAPI().getAlbums().flatMap(new Func1<List<PhotoAlbum>, Observable<GetDriversResponse>>() {
#Override
public Observable<GetDriversResponse> call(GetDriversResponse mResponse) {
return mDatabaseHelper.setMyBus(mResponse);
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<GetDriversResponse>() {
#Override
public void call(List<PhotoAlbum> photoAlbums{view().showSearchResult(photoAlbums); }}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
throwable.printTrack
}
}));

RxAndroid: Observable.first() returns List of null objects

Following this tutorial I've created two sources for fetching data. I expect that if there is no data locally I'll send network request. But all the time get list of null objects from local source (which is first in Observable.concat).
For local source using SQLite with SQLBrite wrapper and Retrofit for remote source:
#Override
public Observable<Item> get(String id) {
//creating sql query
return databaseHelper.createQuery(ItemEntry.TABLE_NAME, sqlQuery, id)
.mapToOneOrDefault(mapperFunction, null);
}
There is method in repository for concating observables:
#Override
public Observable<Item> get(String id) {
Observable<Item> local = localDataSource
.get(id)
.doOnNext(new Action1<Item>() {
#Override
public void call(final Item item) {
// put item to cache
}
});
Observable<Item> remote = remoteDataSource
.get(id)
.doOnNext(new Action1<Item>() {
#Override
public void call(final Item item) {
// save item to database and put to cache
}
});
return Observable.concat(local, remote).first();
}
For getting it with list of ids I'm using next method:
#Override
public Observable<List<Item>> getList(final List<String> ids) {
return Observable
.from(ids)
.flatMap(new Func1<String, Observable<Item>>() {
#Override
public Observable<Item> call(String id) {
return get(id);
}
}).toList();
}
And subscription in fragment:
Subscription subscription = repository
.getList(ids)
.flatMap(new Func1<List<Item>, Observable<Item>>() {
#Override
public Observable<Item> call(List<Item> result) {
return Observable.from(result);
}
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Item>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Item> result) {
// there get list of null objects
}
});
So, main goal is first check local storage and if there is no item - make request to server. But now if item isn't exist I get null instead of send request.
Could someone help me understand why?
Calling first() after concat will of course return the first item, regardless if it's valid.
Yout first() function should validate the value and only submit a 'valid' first item.
Something like:
public void test() {
Integer[] ids = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Observable.from(ids)
.flatMap(new Func1<Integer, Observable<String>>() {
#Override
public Observable<String> call(Integer id) {
return get(id);
}
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<String>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<String> strings) {
}
});
}
public Observable<String> get(int id) {
return Observable.concat(getLocal(id), getRemote(id))
.first(new Func1<String, Boolean>() {
#Override
public Boolean call(String s) {
return s != null;
}
});
}
public Observable<String> getLocal(int id) {
return Observable.just(id < 5 ? "fromLocal id:"+id : null);
}
public Observable<String> getRemote(int id) {
return Observable.just(id >= 5 ? "fromRemote id:"+id : null);
}
Will bring u the result:
fromLocal id:1
fromLocal id:2
fromLocal id:3
fromLocal id:4
fromRemote id:5
fromRemote id:6
fromRemote id:7
fromRemote id:8
fromRemote id:9
fromRemote id:10
Answer from github:
Your function Observable get() returns endless Observable because
mapToOneOrDefault does not complete Observable on its own and reacts to
updates of the db. You need to limit emission of this Observable because
operator concat waits for onCompleted event.
For example, it should looks like:
return Observable.concat(localSource.first(), remoteSource)
.filter(new Func1<Item, Boolean>() {
#Override
public Boolean call(Item item) {
return item!= null;
}
});

Realm access from incorrect thread when Observable subscribed on Schedulers.io()

I am subscribed on IO scheduler.
getObservableItems(itModel).subscribeOn(Schedulers.io()).
onBackpressureBuffer().
observeOn(AndroidSchedulers.mainThread()).
subscribe(new Observer<List<ItemModel>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<ItemModel> resultItemModel) {
}
});
This is my observable where I am doing Realm transactions
private Observable<List<ItemModel>> getObservableItems(ItModel itModel) {
return Observable.create(subscriber -> {
realm = Realm.getInstance(mContext);
if (itModel != null) {
ArrayList<String> ids = ProjectUtil.getId(itModel.getRequestUrl());
DatabaseHelper.saveItemCategory(realm, itModel, ids.get(0), ids.get(1));
}
RealmQuery<ItemModel> itemModelRealmQuery = realm.where(ItemModel.class);
/* Error on below line */
resultItemModel = itemModelRealmQuery.equalTo("res_id", subCategoryModel.getId()).
equalTo("menu_grp_id", subCategoryModel.getMenu_grp_id()).findAll();
subscriber.onNext(resultItemModel);
subscriber.onCompleted();
});
}
It is because you are combining .subscribeOn(Schedulers.IO() with observeOn(AndroidSchedulers.mainThread()).
This means that you execute the work on the io() thread but try to use the result on the main thread. This currently goes against Realms threading policy as we use Thread confined objects. We have an issue for supporting this use case here: https://github.com/realm/realm-java/issues/1208
Until then you will either have to copy your Realm data to standard Java objects or do all realm operations on the same thread (by removing subscribeOn/observeOn).
After update 0.87.1, Realm provide RxJava support.
realm.where(ItemModel.class).findAllAsync().asObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer < RealmResults < ItemModel >> () {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(RealmResults < ItemModel > itemModels) {
System.out.println(itemModels.get(0).getItem());
}
});

Categories

Resources