RxJava Single and Room Error handling Problem - android

I am having this issue regarding empty database return with Room and RxJava Single.
I know that my database is empty, so I am expecting to get an empty return when I trigger
#Query("SELECT * FROM Times WHERE timestamp = :timestamp")
fun getTimes(timestamp: String): Single<Times>
The problem is when I call this function as below
timeDao.getTimes("1398332113")
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnError { Log.e("Single", "Returned null") }
.doOnSuccess { result -> times = result}
.subscribe()
The subscriber is indeed calling doOnError method like
E/Single: Returned null
but still returning an exception and crash
W/System.err: io.reactivex.exceptions.OnErrorNotImplementedException:
Query returned empty result set: SELECT * FROM Times WHERE timestamp =
?
I have seen so many similar questions on StackOverflow, but couldn't find an answer. What am I am doing wrong?

First a solution for your problem. Since version 2.1.0-alpha01 Room supports the Maybe return type which is perfect for modelling your problem.
From the documentation:
The Maybe operates with the following sequential protocol: onSubscribe (onSuccess | onError | onComplete)?
When your the item is in the db: onSuccess will be called with the data. If the db is empty onComplete will be called. onError is self explanatory.
Replacing the Single with Maybe in the Dao class will work.
Further notes:
timeDao.getTimes("1398332113")
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnError { Log.e("Single", "Returned null") }
.doOnSuccess { result -> times = result}
.subscribe()
doOnError
doOnError will execute the the lambda in case of en error, but it will still emit the error down the chain. Your program crashes because it doesn't handle errors (the subscribe() is empty).
You could do subscribe({/* doing nothing for success */}, {e -> Log.e("TAG", "Empty DB")}) to prevent the crash.
default value
If the goal is to return some default value in case the DB is empty then you will want to use onErrorReturn so that the chain continues. Take a look at this blog post for more info.
Null values
The default value can't be null, the Reactive Streams specification (RxJava2 implements it) doesn't support null values.

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.

RxJava Subscribing to many observables does not trigger onNext() for all subscribers?

When I create 5 observables and subscribe to each of them with separate subscriber, intuitively I thought that each subscriber would get its observables' corresponding data, emitted via onNext() call:
val compositeSubscription = CompositeDisposable()
fun test() {
for (i in 0..5) {
compositeSubscription.add (Observable.create<String>(object : ObservableOnSubscribe<String> {
override fun subscribe(emitter: ObservableEmitter<String>) {
emitter.onNext("somestring")
emitter.onComplete()
}
}).subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Logger.i("testIt onNext")
}, {
Logger.i("testIt onError")
}))
}
}
However, what I see is one or two "testIt onNext" in the log.
Now, when I add the delay in subscribers' onNext(), all 6 subscribers onNext() are getting called.
This seems like some racy condition, when some of the subscribers are not fast enough to catch up on their data. Just how this happens evades me, as subscribe() should be called after Subscriber is up and running.
Would be grateful for any tips on this.
Judging from this code every subscriber should print "testIt onNext". Are you sure it is not getting printed? Maybe Android Studio is collapsing identical lines? Have you tried printing something different for each subscriber?

RxAndroid Observable method call with error handling

I am very new to RxAndroid and am still trying to navigate my way out of the errors that I am making.
Observable.just(RandomComputeManager.getChartData(0,"abcd",new Date()))
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {
System.out.println("RXANDROID"+ s.getFood());
Toast.makeText(getActivity(),"HELLO"+s.getFood(),Toast.LENGTH_LONG);
});
I have a heavy computation method here that I am trying to run on RxJava's Schedulers.computation() thread.(I do not know if only calling it in Observable.just is the right way). The method is supposed to throw an exception if it does not have data.
Class RandomComputeManager{
public static getPieChartData(int a,String b,Date c) throws CustomException {
if(haveData){
//All Okay
}
else{
throw new CustomException("No Data");
}
}
The build is failing with error
error: unreported exception CustomException; must be caught or declared to be thrown
I have tried adding an observer to the subscribe method thinking that it has a onError method but neither is that solving this issue nor am I able to fetch my data then due to some ambiguity in the return value of the called method(Don't know if it should be an observable or just the object I need).
Please suggest a way to handle this.
The subscriber function can take another argument of throwable.
Please do like this
Observable.just(RandomComputeManager.getChartData(0,"abcd",new Date()))
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {
System.out.println("RXANDROID"+ s.getFood());
Toast.makeText(getActivity(),"HELLO"+s.getFood(),Toast.LENGTH_LONG);
}, throwable ->{
});
Observable.fromCallable(RandomComputeManager.getChartData(0,"abcd",new Date()))
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {
System.out.println("RXANDROID"+ s.getFood());
Toast.makeText(getActivity(),"HELLO"+s.getFood(),Toast.LENGTH_LONG);
}, Throwable :: printStackTrace);
This did the trick. Thanks to Gautam Kumar and Raj Suvariya for your help. I found this detail from Exception handling in rxjava
just method accepts parameters that are resolved immediately, so you actually are about to run the computation at the very same line you create your Observable. Also, this is the reason that your exception is not caught, as the getChartData is not called within Observable on-subscribe function.
What you need here is to create the Observable passing a computation function, but you are trying to pass the computed result.
I am used to Kotlin, so sorry if I mess up Java lambdas here, but you should use fromCallable here, like so
Observable.fromCallable(
() -> RandomComputeManager.getChartData(0, "abcd", new Date())))
fromCallable accepts a function that will execute once you subscribe and will emit the function result.
Also, for your purpose it's better to use a Single, as you will have only one item emitted.
Single.fromCallable(
() -> RandomComputeManager.getChartData(0, "abcd", new Date())))
Also if your your CustomException is checked and you don't want to crash, you do have to add onError handling, like already suggested by others.

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

Categories

Resources