RxJava data flow with SqlBrite and Retrofit - android

In my Android app I am using domain level Repository interface, which is backed with local DB implemented using SqlBrite and network api with Retrofit observables. So I have method getDomains(): Observable<List<Domain>> in Repository and two corresponding methods in my Retrofit and SqlBrite.
I don't want to concatenate or merge, or amb these two observables. I want my Repository to take data only from SqlBrite and since SqlBrite returns QueryObservable, which triggers onNext() every time underlying data changed, I can run my network request independently and store results to SqlBrite and have my Observable updated with fetched from network and stored to DB data.
So I tried to implement my Repository's getDomains() method as follow:
fun getDomains(): Observable<List<Domain>> {
return db.getDomains()
.doOnSubscribe {
networkClient.getDomains()
.doOnNext { db.putDomains(it) }
.onErrorReturn{ emptyList() }
.subscribe()
}
}
But in this case every time the client should subscribe, every time it would make network requests, that is not so good. I thought about other do... operators to move requests there, but doOnCompleted() in case of QueryObservable would never be called, until I call toBlocking() somewhere, which I won't, doOnEach() also not good as it makes requests every time item from db extracted.
I also tried to use replay() operator, but though the Observable cached in this case, the subscription happens and results in network requests.
So, how can combine these two Observables in the desired way?

Ok, it depends on the concrete use case you have: i.e. assuming you want to display the latest data from your local database and from time to time update the database by doing a network request in the background.
Maybe there is a better way, but maybe you could do something like this
fun <T> createDataAwareObservable(databaseQuery: Observable<T>): Observable<T> =
stateDeterminer.getState().flatMap {
when (it) {
State.UP_TO_DATE -> databaseQuery // Nothing to do, data is up to date so observable can be returned directly
State.NO_DATA ->
networkClient.getDomains() // no data so first do the network call
.flatMap { db.save(it) } // save network call result in database
.flatMap { databaseQuery } // continue with original observable
State.SYNC_IN_BACKGROUND -> {
// Execute sync in background
networkClient.getDomains()
.flatMap { db.save(it) }
.observeOn(backgroundSyncScheduler)
.subscribeOn(backgroundSyncScheduler)
.subscribe({}, { Timber.e(it, "Error when starting background sync") }, {})
// Continue with original observable in parallel, network call will then update database and thanks to sqlbrite databaseQuery will be update automatically
databaseQuery
}
}
}
So at the end you create your SQLBrite Observable (QueryObservable) and pass it into the createDataAwareObservable() function. Than it will ensure that it loads the data from network if no data is here, otherwise it will check if the data should be updated in background (will save it into database, which then will update the SQLBrite QueryObservable automatically) or if the data is up to date.
Basically you can use it like this:
createDataAwareObservable( db.getAllDomains() ).subscribe(...)
So for you as user of this createDataAwareObservable() you always get the same type Observable<T> back as you pass in as parameter. So essentially it seems that you were always subscribing to db.getAllDomains() ...

if your problem is that you have to subscribe your observer every time that you want to get data you can use relay, which never unsubscribe the observers because does not implement onComplete
/**
* Relay is just an observable which subscribe an observer, but it wont unsubscribe once emit the items. So the pipeline keep open
* It should return 1,2,3,4,5 for first observer and just 3, 4, 5 fot the second observer since default relay emit last emitted item,
* and all the next items passed to the pipeline.
*/
#Test
public void testRelay() throws InterruptedException {
BehaviorRelay<String> relay = BehaviorRelay.create("default");
relay.subscribe(result -> System.out.println("Observer1:" + result));
relay.call("1");
relay.call("2");
relay.call("3");
relay.subscribe(result -> System.out.println("Observer2:" + result));
relay.call("4");
relay.call("5");
}
Another examples here https://github.com/politrons/reactive/blob/master/src/test/java/rx/relay/Relay.java

Related

How to obtain onComplete with Room Flowable<List<Item>>?

I have an Android app using Room to save my favorites.
Here is my DAO :
#Query("SELECT * FROM favorites ORDER BY lastConsultation DESC")
fun getAll() : Flowable<List<Favorite>>
I want to use Flowable to enable my MainActivity to be notified every time a favorite is added or removed.
On my MainActivity, I want to retrieve all my favorites and make a network request to check some information about my favorite.
In my UseCase, I have the following piece of code to make my call
favoritesRepository.getAll()
.flatMap { Flowable.just(it) }
.concatMapEager { Flowable.fromIterable(it) }
.concatMapEager {
itemRepository.getItem(it.id)
.toFlowable()
}
.toList()
.toFlowable()
The itemRepository returns a Single when getItem is called. I retrieve a Flowable<List<Favorite>> from my favoritesRepository and want to turn each Favorite in an Item after making a network request, my method returning a Flowable<List<Item>>
I thought that adding .flatMap { Flowable.just(it) } would create a new Flowable that would emit onComplete once the item has been emitted (since Room will not emit onComplete).
That piece of code is not working, the onComplete is never called so the .toList() is not called either.
Is there a way I could achieve those calls (with concurrency hence the concatMapEager) while keeping my Flowable implementation (I could use a Single to be rid of the problem but I would lose the "auto notification" on the MainActivity) ?
I'll make an assumption that realtyRepository returns a Single. Try this.
favoritesRepository
.getAll()
.switchMapSingle { Flowable
.fromIterable(it)
.concatMapEager { realtyRepository.getRealty(it.id).toFlowable() }
.toList()
}
This will get what I understand you want, yet you'll still get notified when DB changes. Also, switchMapSingle will make sure that whatever work you have ongoing, will get cancelled if any DB updates occurs in the meantime.

How to combine network and database updates in RxJava in Android application

I am investigating the use of RxJava in my latest Android application.
I have a two lists of related updated Database model objects
ListDB1 and ListDB2
the logic I am attempting to implement is as follows
1). For each item in ListDB1
1.1). Transform it to a Network model object
1.2). Execute an Update RESTful API
2). Once all network updates have completed successfully
2.1). Persist ListDB1 to my local database.
2.2). Persist ListDB2 to my local database.
So far I have this code which should call my network API's
Observable.just(getDatabaseList())
.subscribeOn(Schedulers.io())
.flatMapIterable(x -> x)
.flatMap(database -> transformDatabase(database, DB_1_MAPPER))
.doOnNext(NetworkController::updateRemote)
.observeOn(AndroidSchedulers.mainThread())
.doOnComplete(getOnComplete())
.doOnError(getOnError())
.subscribe();
No API calls are executed though
I would rather use Single that Observable as my API calls respond with Single<Response>, however I couldnt see how to achieve Observable.just(<>) with a Single.
Also I cannot see how to commence my Rx process by processing each List item separately for the Network calls, then performing Database calls with a complete list as I am using Realm as my local database which can accept lists of database objects in a single update.
In pseudo code my process resembles:
for each database list item
convert to network model item
call remote update API
When all all network calls are successful
update database with ListDB1
update database with ListDB2
end
Is this possible in one Rx process "stream"?
1.
I would rather use Single that Observable as my API calls respond with Single, however I couldnt see how to achieve Observable.just(<>) with a Single.
You can do this:
Single.just(getDatabaseList()) // Single<>
However, Single is not suitable in this case because you are not working with a single item, and what you need instead is to iterate through multiple items and work on the items one by one.
2.
processing each List item separately for the Network calls, then performing Database calls with a complete list
You can use toList() operator which emits entire list of items when the observable completes.
3.
The purpose of do operators such as doOnNext, doOnComplete, and doOnError is to create side effect that does not affect the stream. An example of this kind of operations is logging. You should not do any meaningful operation that affect the stream in such operators.
Instead you should be using operators such as map, flatMap, etc.
4.
Putting everything together:
Observable.fromIterable(getDatabaseList())
.subscribeOn(Schedulers.io())
.flatMap(database -> transformDatabase(database, DB_1_MAPPER))
.flatMap(NetworkController::updateRemote)
.toList() // This has type of Single<List<>>
.flatMap(list -> {
// Update db1 and db2 with the result of server update.
return Single.zip(updateDb1(list), updateDb2(list), (a, b) -> {
// Combine result of db updates
});
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
// Handle success case
}, err -> {
// Handle error case
});

RxJava: concatArrayEager not emitting result from repository for offline case when concatArray is

I have an app with offline functionality that requests data from a repository for getting data from a local database and an api request. The api call updates the local database so that in case the app is offline data is stored in the db is still displayed.
My ProjectRepository class has a function getAllProjectByUserId which returns an Observable which combines output from two sources (the database and api call)
fun getAllProjectByUserId(userId: Int): Observable<Projects> {
// concatArray takes care of first delivering the db entry to subscribers and secondly data from api
return Observable.concatArrayEager(
getAllProjectsByUserIdFromDb(userId),
getAllProjectsByUserIdFromApi(userId))
}
This function is called in the DataProvider:
fun getAllProjectByUserId(userId: Int, fetchDataErrorListener: FetchDataErrorListener): Observable<Projects> = projectRepository.getAllProjectByUserId(userId).handleErrorFilterAndDebounce(fetchDataErrorListener)
When the data from both sources are requested, the dataprovider takes care of only emitting the observables that didn't cause an onError response (see .filter operator) and debouncing (e.g. ignoring) the database observable emission if the api observable is emitted within the API_FETCH_DEBOUNCE_TIMEOUT (in my case 300ms).
The code that takes care of this looks like:
private fun <T> Observable<T>.handleErrorFilterAndDebounce(onRepoErrorListener: FetchDataErrorListener): Observable<T> {
return this
// materialize wraps the observed object types into a Notification object on which we can check
// whether the onNext, onError and/or onComplete methods are called. This is used in map function
// to check if an error occurred and invoke error callback in presenter
.materialize()
// since map operator can encounter an error that triggers a callback handling an UI event we need to make sure
// the error is handled (observedOn) the main thread
.observeOn(AndroidSchedulers.mainThread())
.map {
// if the observables onError is called, invoke callback so that presenters can handle error
it.error?.let {
Logging.logError(CLASS_TAG, " error localized message: " + it.localizedMessage + " cause.throwable=" + it.cause)
handleErrorCallback(it, onRepoErrorListener)
}
// put item back into stream
it
}
.filter {
Logging.logError(CLASS_TAG, " it.isOnError = " + it.isOnError)
// Only return observable on which onError is not called.
// This way if api call returns an error (within debounce timeout) the database item won't be ignored
// and wil be received by all subscribers
!it.isOnError
}
// reverses the effect of .materialize()
.dematerialize<T>()
//Drop DB data if we can fetch item from API fast enough to avoid UI flicker using the specified timeout
.debounce(API_FETCH_DEBOUNCE_TIMEOUT, MILLISECONDS)
}
Now here's my problem:
I would like to use the .concatArrayEager instead of the .concatArray operator since concatArrayEager starts both source requests at same time instead of consecutive, thus the whole operation should take less time.
But: Given that there is no network connection and i do have data stored in the local database.
When I use .concatArray, subscribing to the observable that the DataProvider.getAllProjectsByUserId() function returns does in fact give me the data from the database
If i change the operator to use .concatArrayEager instead of .concatArray no data seems to be emitted. E.g. the subscriber is not receiving any data. Is this a RxJava bug, or am I missing something here?
concatArrayEager or concatArray will terminate if one of the sources encounter error , Instead you should use concatArrayDelayError as per documentation :
Concatenates a variable number of ObservableSource sources and delays errors from any of them till all terminate.
source here

Chaining multiple RxJava2 operations using Room and Firebase

Some background: I'm new to RxJava and I'm trying to make a feature in the app that will work offline and sync when there is network. I'm trying to chain multiple operations but I'm not well versed in how to chain different types like Completable, Maybe and Observable together.
Here are the list of operations that need to be done in the order after user adds or updates some data:
Update data on local db, just set the status to syncing, using Room here.
Upload the file to Firebase storage.
Get the file url and update the data to Firebase Database.
Update data on local db, set the status to synced.
Here are the methods for each operation:
Update local db:
private Completable setStatus(Entity entity, Entity.Status status){
entity.setStatus(status);
return Completable.fromAction(() -> localDataStore.updatePersonalPlace(personalPlaceEntity));
}
Upload file to FirebaseStorage, using Rx2Firebase
RxFirebaseStorage.putBytes(storageRef, bytes); // returns a Maybe<TaskSnapshot>
Set data in firebase database
RxFirebaseDatabase.setValue(dataRef, model); // returns a Completable
I've tried
setStatus(...)
.toObservable()
.map(o -> uploadFile())
.map(fileUrl -> updateFirebaseDatabase(fileUrl))
.doOnNext(() -> setStatus(..) ) // set status to synced
.subscribe(() -> Timber.d("Data updated",
t -> setStatus(..)); // set status back to what it was on error
But this doesn't work and I think I don't really understand the fundamentals of how to chain these operations. None of the operations after toObservable get called.
I've also tried to convert the maybe to a completable and chain them using Completable.andThen but I'm not sure how to do that correctly and I need the fileUrl returned to update the firebase database.
Could someone please point me in the right direction as to what should I use here. It's a fairly simple task which feels a lot complicated right now, maybe my approach is horribly wrong.
Thanks,
I add some comments to your code:
setStatus(...) // completable => (onError|onComplete)?
.toObservable() // => will call (onError|onComplete)? (beacause of the nature of completable)
.map(o -> uploadFile()) // never call because no item is emitted (completable...)
.map(fileUrl -> updateFirebaseDatabase(fileUrl)) // never call too
.doOnNext(() -> setStatus(..) ) // set status to synced // never call too
.subscribe(..)
You have to change your Completable for a Single and returning something like true.
Set status returns a Completable, which will only ever call onComplete or onError. Your map and doOnNext never get called because it never emits any items. What you probably want use doOnComplete or look into using concatArray, startWith or concatWith that can chain Completables.
Thanks to answers from Kevinrob and cwbowron I was able to figure out what was going wrong.
setStatus now returns a Single:
private Single<Integer> setStatus(Entity entity, Entity.Status status){
entity.setStatus(status);
return Single.fromCallable(() -> localDataStore.updatePersonalPlace(personalPlaceEntity));
}
This returns a completable which:
Sets the entity status as syncing in local db.
Converts a bitmap to byte array.
Uploads the photo on Firebase Storage.
Gets the photo url.
Updates the data on Firebase Database.
Finally updates the entity status as synced in local db.
return setPlaceStatusSingle(entity, Entity.Status.SYNCING)
.subscribeOn(Schedulers.io())
.toObservable()
.map(integer -> BitmapUtils.convertBitmapToByteArray(entity.getPhoto()))
.doOnNext(bytes -> Timber.d("Converted bitmap to bytes"))
.flatMap(bytes -> RxFirebaseStorage.putBytes(fileRef, bytes).toObservable())
// Using flatmap to pass on the observable downstream, using map was a mistake which created a Single<Observable<T>>
.observeOn(Schedulers.io())
.doOnNext(taskSnapshot -> Timber.d("Uploaded to storage"))
.map(taskSnapshot -> taskSnapshot.getDownloadUrl().toString()) // Firebase stuff, getting the photo Url
.flatMapCompletable(photoUrl -> {
Timber.d("Photo url %s", photoUrl);
model.setPhotoUrl(photoUrl);
return RxFirebaseDatabase.setValue(ref, model);
})
// Passes the Completable returned from setValue downstream
.observeOn(Schedulers.io())
.doOnComplete(() -> {
entity.setStatus(Entity.Status.SYNCED);
entity.setPhotoUrl(model.getPhotoUrl());
localDataStore.updateEntity(entity);
})
.doOnError(throwable -> onErrorUpdatingEntity(entity, throwable));

Conditional chain of observables

I want to asynchronously retrieve data via multiple REST APIs. I'm using Retrofit on Android with the rxJava extension, i.e. I execute any GET request by subscribing to an Observable.
As I said, I have multiple source APIs, so when the first source does not yield the desired result I want to try the next on, if that also fails, again try the next and so forth, until all sources have been queried or a result was found.
I'm struggling to translate this approach into proper use of Observables since I don't know which operators can achieve this behaviour and there are also some constraints to honor:
when a result has been found, the remaining APIs, if any, should not be queried
other components depend on the result of the query, I want them to get an Observable when starting the request, so this Observable can notify them of the completion of the request
I need to keep a reference to aforementioned Observable because the same request could possibly be made more than once before it has finished, in that case I only start it the first time it is wanted and subsequent requests only get the Observable which notifies when the request finished
I was starting out with only one API to query and used the following for the request and subsequent notification of dependent components:
private Observable<String> loadData(int jobId) {
final ConnectableObservable<String> result = Async
.fromCallable(() -> getResult(jobId))
.publish();
getRestRequest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
dataHolder -> {
if (dataHolder.getData() != null && !dataHolder.getData().isEmpty()) {
saveData(dataHolder.getData());
} else {
markNotFound(dataHolder);
}
},
error -> currentJobs.remove(jobId),
() -> {
currentJobs.remove(jobId);
result.connect();
});
return result;
}
This code was only called for the first request, the returned Observable result would then be saved in currentJobs and subsequent requests would only get the Observable without triggering the request again.
Any help is highly appreciated.
Assuming you have a set of observables that re-connect each time you subscribe:
List<Observable<Result>> suppliers = ...
Then you just need to do the logical thing:
Observable<Result> results = Observable
.from(suppliers)
.concatMap(supplier -> supplier)
.takeFirst(result -> isAcceptable(result))
.cache()
Use .onErrorResumeNext, and assuming that each service observable may return 0 or 1 elements use first to emit an error if no elements are emitted:
Observable<T> a, b, c;
...
a.first().onErrorResumeNext(t -> b.first())
.onErrorResumeNext(t -> c.first())
.onErrorResumeNext(t -> d.first())
...

Categories

Resources