RxJava chain to a database and a server - android

I'm trying to create a good chain. I think this question is popular and has been asked, but I haven't found it.
So what I want is to go to the database and if I don't have anything inside it, I want to download info from the server.
Here is the code:
() -> loadPriceListFromDatabase.call(null)
.flatMap(mealTypes -> {
if (mealTypes.size() != 0 ) {
return Observable.just(mealTypes); //here is the place where I'm not sure about code style.
}
return loadPriceList.call(null)
.map(model -> {
preferences.setPriceListDate(model.getDate().getTime());
return ActiveAndroidHelper.saveMeals(model);
});
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
MainActivity::onTestLoaded,
BaseActivity::onError
So how should I do this? Any must use examples?
UPD
I want to put any condition I want inside if\else block.
if (mealTypes.size() != 0 ) {
not only something is null or zero.
For example I can say
if (date == currentDate) { (//where date is timestamp

It's as simple as:
Observable.concat(
databaseObservable.filter(databaseItem -> databaseItem.date == currentDate) ,
networkObservable
).take(1).subscribe(...)
It won't subscribe to the networkObservable unless your databaseObservable completes without onNext.

Use swtichIfEmpty operator, in case your observable is empty automatically you can provide a new one in the pipeline.
Check this example
#Test
public void testSwitchPerson() {
Observable.just(new ArrayList<>())
.flatMap(persons -> Observable.from(persons)
.switchIfEmpty(Observable.just(new Person("new", 0, "no_sex"))))
.subscribe(System.out::println);
}
if you want to see more practical example check here https://github.com/politrons/reactive

Related

RxJava combine database create/update call for a list of objects

im currently struggeling with my first more complex rx chain, maybe you can help me out.
I have a list of objects, which i get from an api call. I need to check if they exists local, if they do, update the dataset, if not create them. My current approach is something like this:
private fun insertUpdateFromServer(objectsToInsert: List<Model>): Completable {
return Observable.fromIterable(objectsToInsert).flatMapCompletable { atl ->
dao.getByServerId(atl.id).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.switchIfEmpty { obs: SingleObserver<in Model> -> createNewObject(atl) }.flatMapCompletable {
updateObject(atl, it).applySchedulers()
}
}
Its important, that i have to wait for all the subtasks to complete. This is working if i only have objcts to update, but if there is a new object, the whole thing will not complete.
Note that im only interested if the operation is completed, the emitted object doesnt matter for me. The function "getByServerId" returns a Maybe.
So im asking you if you can point out my logical mistakes and push me in the right direction, thanks in advance!
The problem is with this line:
switchIfEmpty { obs: SingleObserver<in Model> -> createNewObject(atl) }
Kotlin's automatic SAM conversion is trying to make your life easier by generating an overload of switchIfEmpty that implements void subscribe from SingleSource. In not all cases is this helpful however, what you want to be using instead is this:
switchIfEmpty(createNewObject(atl))
A better explanation of why this is happening can be found here.
So i ended up with a workaround which i found here.
TL;DR: Maybe emits null and calls then "onComplete". So i can chain this if i use "onSuccess" and "onComplete".
The code for someone who is interested:
private fun insertUpdateFromServer(objects: List<Model>): Completable {
return Observable.fromIterable(objects)
.flatMapCompletable { obj ->
dao.getByServerId(obj.id).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.doOnComplete { createNewObject(obj).subscribe() }
.doOnSuccess { updateObject(obj, it).applySchedulers().subscribe() }
.flatMapCompletable { Completable.complete() }
}
}
This expression waits for all it subtasks to terminate and terminates then completely.

How to Listen to more than Three Fields in Observables.combineLatest

I have code to listen to exactly three fields using Observables.combineLatest
Observables.combineLatest(text_name.asObservable(),text_username.asObservable(), text_email.asObservable()).subscribe({ t ->
if (t.first.toString() != currentName || t.second.toString() != currentUsername) {
startActionMode()
} else {
finishActionMode()
}
})
but when I add another parameter to the Observables.combineLatest it throughs error since only 3 inline-parameters can be passed..
Now I would wish to pass 4 parameters in the parameter list for Observables.combineLatest.. I know it should be done using an array or a list, passed in as parameter but It's hard for me to figure it out using Kotlin.
Help me out.. Thanks in Advance..
You need to add a combine function if you want to combine more than 3 observables. You can do something like this.
Observables.combineLatest(
first.asObservable(),
second.asObservable(),
third.asObservable(),
forth.asObservable()
)
// combine function
{ first, second, third, forth->
// verify data and return a boolean
return#subscribe first.toString() != currentName || second.toString() != currentUsername
}
.subscribe({ isValid->
if (isValid) {
startActionMode()
} else {
finishActionMode()
}
})
In the combine function you can verify your data and return a boolean.
Then in subscribe you can take an action based on that boolean

Rx concatWith() return only first Flowable result

I have posted all methods they are working separately , but I face issues with the first one, where I concatWith() two flowables
return userFavouriteStores()
.concatWith(userOtherStores())
.doOnNext(new Consumer<List<StoreModel>>() {
#Override
public void accept(#io.reactivex.annotations.NonNull List<StoreModel> storeModels) throws Exception {
Log.i("storeModels", "" + storeModels);
}
})
public Flowable<List<StoreModel>> userFavouriteStores() {
return userStores()
.map(UserStores::favoriteStores)
.flatMap(storeId -> storeDao.storesWithIds(storeId))
.map(stores -> { // TODO Konvert to Kotlin map {}
List<StoreModel> result = new ArrayList<>(stores.size());
for (se.ica.handla.repositories.local.Store store : stores) {
result.add(store.toStoreModel(StoreModel.Source.Favourite));
}
return result;
}); }
public Flowable<List<StoreModel>> userOtherStores() {
return userStores().map(UserStores::otherStores)
.flatMap(storeId -> storeDao.storesWithIds(storeId))
.map(stores -> {
List<StoreModel> result = new ArrayList<>(stores.size());
for (Store store : stores) {
result.add(store.toStoreModel(StoreModel.Source.Other));
}
return result;
});}
updated method :userStores() is used for favorite and other stores ,
private Flowable<UserStores> userStores() {
return apiIcaSeResource
.userStores()
.toFlowable(); }
#GET("user/stores")
Single<UserStores> userStores();
Following the comments follow up, and additional information, you don't have a problem specifically with the concat(), I'm assuming it is work, it's just not the tool for what you want to achieve here.
concat() will not concatenate two lists to a single list, but rathe will first emit all items by first Flowable and only then items emitted by second Flowable (hence you must have onComplete so concat will know when Flowable is end, what I asked in the begining).
in order to combine the lists together, I would suggest to zip both stores Obesrvables (favorites/ others), and then simply combine to list to have single output of combined list.
Besides that, as you pointed out, as both stores Observables comes from userStores(), you will invoke the network request twice, which definitely not necessary. you can solve it using publish(), that will share and multicast the network result to both Observables, resulting with single network request.
to sum it up, I would rather recommend to use Single here, not Flowable as you don't have backpressure consecrations. something like the following implementation:
Observable<List<StoreModel>> publish = userStores()
.toObservable()
.publish(userStores ->
Single.zip(
userFavouriteStores(userStores.singleOrError()),
userOtherStores(userStores.singleOrError()),
(favoriteStores, otherStores) -> {
favoriteStores.addAll(otherStores);
return favoriteStores;
}
)
.toObservable()
);

How can I base method return value on Subscription?

I have a small code sample that returns some data from api or from database, depending on with one from above contains any data.
Now I want to add another condition based on Network status. I use ReactiveNetwork library for this but I can't make it work, this is what I tried:
#Override public Observable<List<Tactic>> getTactics(boolean forceRefresh) {
Observable<List<Tactic>> diskObservable =
disk.get().getTactics().compose(RxDataUtils.applyLog(SourceType.DISK));
Observable<List<Tactic>> apiObservable = api.get()
.getTactics()
.doOnNext(tactics -> disk.get().save(tactics))
.compose(RxDataUtils.applyLog(SourceType.API));
Subscription connectivityStatusSubscription = new ReactiveNetwork().observeConnectivity(context)
.compose(RxUtils.applyStandardSchedulers())
.subscribe(connectivityStatus -> {
isNetworkAvailable = RxDataUtils.isConnected(connectivityStatus);
Timber.v("ConnectivityChanged: " + isNetworkAvailable);
});
if (isNetworkAvailable) return forceRefresh ?
apiObservable : Observable.concat(diskObservable, apiObservable).first(
tactics -> (tactics != null && tactics.size() > 0));
return diskObservable;
}
It didn't log anything at all
You should be combining the status stream with the others, not creating two separate subscriptions - otherwise it doesn't really react to the network connectivity.
What you probably want to use is switchMap. Every time the network connectivity changes it can update which stream you're using.
In broad strokes, it'd go something like this:
connectivityObservable.switchMap(isConnected -> {
if (isConnected) {
return networkObservable;
}
return diskObservable;
}
(It looks like yours would be a bit more complex, I just want to lay out the general idea here.)

RxJava + retrofit, get a List and add extra info for each item

I'm playing around with RXJava, retrofit in Android. I'm trying to accomplish the following:
I need to poll periodically a call that give me a Observable> (From here I could did it)
Once I get this list I want to iterate in each Delivery and call another methods that will give me the ETA (so just more info) I want to attach this new info into the delivery and give back the full list with the extra information attached to each item.
I know how to do that without rxjava once I get the list, but I would like to practice.
This is my code so far:
pollDeliveries = Observable.interval(POLLING_INTERVAL, TimeUnit.SECONDS, Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR))
.map(tick -> RestClient.getInstance().getApiService().getDeliveries())
.doOnError(err -> Log.e("MPB", "Error retrieving messages" + err))
.retry()
.subscribe(deliveries -> {
MainApp.getEventBus().postSticky(deliveries);
});
This is giving me a list of deliveries. Now I would like to accomplish the second part.
Hope I been enough clear.
Thanks
Finally I found a nice way to do it.
private void startPolling() {
pollDeliveries = Observable.interval(POLLING_INTERVAL, TimeUnit.SECONDS, Schedulers.from(AsyncTask.THREAD_POOL_EXECUTOR))
.flatMap(tick -> getDeliveriesObs())
.doOnError(err -> Log.e("MPB", "Error retrieving messages" + err))
.retry()
.subscribe(this::parseDeliveries, Throwable::printStackTrace);
}
private Observable<List<Delivery>> getDeliveriesObs() {
return RestClient.getInstance().getApiService().getDeliveries()
.flatMap(Observable::from)
.flatMap(this::getETAForDelivery)
.toSortedList((d1, d2) -> {
if (d1.getEta() == null) {
return -1;
}
if (d2.getEta() == null) {
return 1;
}
return d1.getEta().getDuration().getValue() > d2.getEta().getDuration().getValue() ? 1 : -1;
});
}
Let's go step by step.
First we create an Observable that triggers every POLLING_INTERVAL time the method getDeliveriesObs() that will return the final list
We use retrofit to get an Observable of the call
We use flatMap to flattern the resut list and get in the next flatmap a Delivery item, one by one.
Then we get the estimated time of arrival set inside the Delivery object and return it
We sort the list to order by estimated time of arrival.
In case of error we print and retry so the interval does not stop
We subscribe finally to get the list sorted and with ETA inside, then we just return it or whatever you need to do with it.
It's working properly and it's quite nice, I'm starting to like rxjava :)
I haven't spent a lot of time with Java 8 lambdas, but here's an example of mapping each object to a different object, then getting a List<...> out at the other end in plain ol' Java 7:
List<Delivery> deliveries = ...;
Observable.from(deliveries).flatMap(new Func1<Delivery, Observable<ETA>>() {
#Override
public Observable<ETA> call(Delivery delivery) {
// Convert delivery to ETA...
return someEta;
}
})
.toList().subscribe(new Action1<List<ETA>>() {
#Override
public void call(List<ETA> etas) {
}
});
Of course, it'd be nice to take the Retrofit response (presumably an Observable<List<Delivery>>?) and just observe each of those. For that we ideally use something like flatten(), which doesn't appear to be coming to RxJava anytime soon.
To do that, you can instead do something like this (much nicer with lambdas). You'd replace Observable.from(deliveries) in the above example with the following:
apiService.getDeliveries().flatMap(new Func1<List<Delivery>, Observable<Delivery>>() {
#Override
public Observable<Delivery> call(List<Delivery> deliveries) {
return Observable.from(deliveries);
}
}).flatMap(...)

Categories

Resources