Here is my use case:
I am developing an app that communicates with a server via a REST API and stores the received data in a SQLite database (it's using it as a cache of some sorts).
When the user opens a screen, the following has to occur:
The data is loaded from the DB, if available.
The app call the API to refresh the data.
The result of the API call is persisted to the DB.
The data is reloaded from the DB when the data change notification is intercepted.
This is very similar to the case presented here, but there is a slight difference.
Since I am using SQLBrite, the DB observables don't terminate (because there is a ContentObserver registered there, that pushes new data down the stream), so methods like concat, merge, etc. won't work.
Currently, I have resolved this using the following approach:
Observable.create(subscriber -> {
dbObservable.subscribe(subscriber);
apiObservable
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(
(data) -> {
try {
persistData(data);
} catch (Throwable t) {
Exceptions.throwOrReport(t, subscriber);
}
},
(throwable) -> {
Exceptions.throwOrReport(throwable, subscriber);
})
})
It seems like it's working OK, but it just doesn't seem elegant and "correct".
Can you suggest or point me to a resource that explains what's the best way to handle this situation?
The solution to your problem is actually super easy and clean if you change the way of thinking a bit. I am using the exact same data interaction (Retrofit + Sqlbrite) and this solution works perfectly.
What you have to do is to use two separate observable subscriptions, that take care of completely different processes.
Database -> View: This one is used to attach your View (Activity, Fragment or whatever displays your data) to the persisted data in db. You subscribe to it ONCE for created View.
dbObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(data -> {
displayData(data);
}, throwable -> {
handleError(throwable);
});
API -> Database: The other one to fetch the data from api and persist it in the db. You subscribe to it every time you want to refresh your data in the database.
apiObservable
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(data -> {
storeDataInDatabase(data);
}, throwable -> {
handleError(throwable);
});
EDIT:
You don't want to "transform" both observables into one, purely for the reason you've included in your question. Both observables act completely differently.
The observable from Retrofit acts like a Single. It does what it needs to do, and finishes (with onCompleted).
The observable from Sqlbrite is a typical Observable, it will emit something every time a specific table changes. Theoretically it should finish in the future.
Ofc you can work around that difference, but it would lead you far, far away from having a clean and easily readable code.
If you really, really need to expose a single observable, you can just hide the fact that you're actually subscribing to the observable from retrofit when subscribing to your database.
Wrap the Api subscription in a method:
public void fetchRemoteData() {
apiObservable
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(data -> {
persistData(data);
}, throwable -> {
handleError(throwable);
});
}
fetchRemoteData on subscription
dbObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(() -> fetchRemoteData())
.subscribe(data -> {
displayData(data);
}, throwable -> {
handleError(throwable);
});
I suggest you really think about all that. Because the fact that you're forcing yourself into the position where you need a single observable, might be restricting you quite badly. I believe that this will be the exact thing that will force you to change your concept in the future, instead of protecting you from the change itself.
Related
I'm doing an API call to get the descriptions of a program podcast and based on the type of podcast, I may or may not have to do another API call to get more information. I'm new to RxJava and I'm not sure the best way to make such calls. Here's what I have so far:
public void fetchFeaturedItems() {
Timber.i("Fetching Featured...");
disposables.add(episodeService.getFeatured()
.subscribeOn(Schedulers.io())
.doOnNext(featured -> { //make second call
final Episode episode = featured.getEpisode();
Observable<Timing> timingObservable = episodeService.getTimingForEpisodeActs(episode);
if (timingObservable != null) {
timingObservable
.subscribeOn(Schedulers.io())
.doOnError(Timber::e)
.subscribe(timing -> {episodeManager.saveTiming(timing);}); //save to database
}
})
.observeOn(Schedulers.io())
.subscribe(featured -> {
saveFeatured(featured);
final Episode episode = featured.getEpisode();
notificationManager.handleNewEpisodeNotification(episode);
}, Timber::e));
}
This all works, but I'm getting a "result of subscribe is not used" lint warning on the second subscribe. I'm not combining results of the two calls. I could really use some guidance.
Use flatMap() instead of onNext(). You get warning about "result of subscribe is not used" cause of second subscribtion. flatMap() should help.
read this first and other RxJava documentation
.doOnNext is a side-effect operator. What you're doing:
timingObservable
.subscribeOn(Schedulers.io())
.doOnError(Timber::e)
.subscribe(timing -> {episodeManager.saveTiming(timing);});
Will just create a disposable. This disposable won't be a part of the stream. Also note that the timingObservable stream will now run totally independently, because as I just said, doOnNext is a side-effect operator. What you're doing is a fire-and-forget call. To make the response as a part of the stream, use a .flatMap in place of .doOnNext. It will merge your responses as they come, and push them to the downstream operators.
Ideally, a reactive stream should be subscribed to only once - you are doing it twice. This is an immediate code smell.
I'm refactoring the implementation of my repositories using RxJava so i want to know some ways to edit, for example, a user.
My getUser(email: String), with email as id, is returning an observable and in the repository implementation i either get the data from database or server, all good by now.
What i want to achieve is editing a user. For that i would have and update(user: User) function, and the naive way to use it would be
userRepository.getUser(email)
.subscribeOn(Schedulers.io())
.subscribe { user ->
user.name = "antoher name"
userRepository.update(user)
.subscribeOn(Schedulers.io())
.subscribe {
//handle response
}
}
Is there a way to avoid this type of call of an observer inside an observer? It is not very readable for me and i guess there's a better way but i'm not getting it.
NOTE: I'm using clean architecture, so i think an update for every field, making me get user in data module is not correct as i would have subscribe to an observer in data, and that difficult the dispose when activity destroys
For me is not the same question as When do you use map vs flatMap in RxJava? because, despite of flatMap being the thing that answer the question, it is not the same question, so anyone who has the same problem/question but don't know that flatmap is the answer, will never reach to use flatmap.
One strength of using RxJava is that you can chain as many async operations (method that would return Observable or Single, repository methods in your case) as you want without falling into callback hells. You see in your code that there are nested subscribe blocks. What if you had to chain more async network operations? You fall into callback hells and the code will become harder to follow and maintain.
Removing nested callbacks and making code more functional, compositional, and readable is one thing RxJava is really good at. In the intro part of ReactiveX website , they mention about this in the intro part of ReactiveX website (http://reactivex.io/intro.html).
Callbacks solve the problem of premature blocking on Future.get() by
not allowing anything to block. They are naturally efficient because
they execute when the response is ready.
But as with Futures, while callbacks are easy to use with a single
level of asynchronous execution, with nested composition they become
unwieldy.
Flatmap operator is to the rescue here. You can look into the definition of flatMap operator in the link below.
http://reactivex.io/documentation/operators/flatmap.html
Below is the code I would use in your case.
userRepository.getUser(email)
.subscribeOn(Schedulers.io())
.map { user -> user.name = "another name"; return user; }
.flatMap { user -> userRepository.update(user) }
.doOnSuccess { /* handle response here */ } // doOnNext if you are using observable
.subscribe({ /* or handle response here */ }, { /* must handle error here */})
Flatmap operator flattens Single of update response which will be returned by your repository's update method and pass just the response downstream. Above code is not only easier to read but also makes your code reusable because update logic is now part of the chain.
Distinguishing between map and flatMap is really important in exploiting the full benefit of RxJava so it will be really beneficial to get used to it!
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));
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())
...
I am seeking an example of a flow I'm trying to implement with help of RxJava.
Suppose I want to show a list of data. The flow should look something like this:
Read cache. If it contains the data, show it;
Send an API request to the server:
If it returned the data, then cache it and show it.
If it returned and error and there was no cached data, then show an error.
If it returned and error and there was something cached, then do nothing.
Right now I have a method that does something similar (with lots of inspiration from Jake's u2020). The main difference is that it uses in-memory caching, which means there's no need for a separate Observable for reading from cache and it can be done synchronously.
I don't know how to combine two observables (one for reading from cache and the other for API call) and obtain the flow described above.
Any suggestions?
I think I solved my problem. The observable chain looks like so:
apiCall()
.map(data -> dataInMemory = data)
.onErrorResumeNext(t -> data == null ?
Observable.just(Data.empty()) : Observable.empty())
.startWith(readDataFromCache().map(data -> dataInMemory = data))
.subscribeOn(ioScheduler)
.observeOn(uiScheduler)
.subscribe(dataRequest);
The main point is, that if readDataFromCache() throws an error, it will call onCompleted() without calling onError(). So it should be a custom Observable which you can control.
Data.empty() is a stub for my data - the Subscriber should treat it as an error.
dataInMemory is a member in my controller which acts as in-memory cache.
EDIT: the solution doesn't work properly. The completion of one use case (see comment) is not achieved.
EDIT 2: well, the solution does work properly after some tweaking. The fix was returning different types of observables depending on the state of in-memory cache. Kind of dirty.
Here is my solution:
readDataFromCache().defaultIfEmpty(null)
.flatMap(new Func1<Data, Observable<Data>>() {
#Override
public Observable<Data> call(final Data data) {
if (data == null) {
// no cache, show the data from network or throw an error
return apiCall();
} else {
return Observable.concat(
Observable.just(data),
// something cached, show the data from network or do nothing.
apiCall().onErrorResumeNext(Observable.<Data>empty()));
}
}
});
I don't add the subscribeOn and observeOn because I'm not sure readDataFromCache() should use ioScheduler or uiScheduler.