Fetching data from DB , Room DAO has a method that returns a Flowable userDao.getInfo(), this Flowable will never completes, I tested adding doOnNext() it emits 5 times (DB contains 5 items) but complete is never called, but I need as I have toList(),what could be the alternative for this
return userDatas()
.flatMapIterable(items -> items)
.flatMap(userData -> userDao.getInfo(userData.getId())
.map(user -> user.toStoreModel(...)//added doOnNext()-works 5 times and doOnComplete()doesn't work
.doOnNext(userData -> Log.i("test",""+userData))
.doOnComplete(() -> Log.i("test","complete"))
.toList()
.map(UserModel::fromUserModels)
.toFlowable();
#Query("SELECT * FROM user WHERE id = :id")
Flowable<...> getInfo(Long Id);
public Flowable<List<UserStore>> userDatas() {
return userDao.allUserDatas()
.take(1)//added complete and next works
.filter(userDatas -> !userDatas.isEmpty())
.switchIfEmpty(userIds()
.doOnNext(userDatas -> userDao.insert(userDatas)));
}
I have tested and even when I'm replacing userDatas() only with userDao.allUserDatas() (I'm sure it exists in DB) it gives the same results
If you need to have called complete method you can use take(1).but in that case you could not listener further DB changes
Everything is ok with your code ,it would never complete
Db Flowables are observable ,so they keep listening if database changes, so it never completes.
Ideally you should fix userDao so that it completes normally. If that is not possible for some reason, you can time it out and map error to empty, forcing completion like so:
userDao.getInfo(userData.getId())
.timeout(1, TimeUnit.SECOND)
.onErrorResumeNext(Observable.empty())
Related
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.
I have a simple insertion in ROOM but for a weird reason my insertion aren't working in my app after two or three insert. There is another weird behaviour, when inserting I have a success insert but when looking in the db, there is nothing.
DAO
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(entryTable: EntryTable): Completable
ViewModel
entryRepository.saveEntryTable(EntryTable(fieldId, entryId, tabId, index))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Timber.d("<<<<<row saved!")
result.value = Status.SUCCESS
}, {
Timber.e(it)
Timber.d("<<<<<<<<<<<row not saved!")
result.value = Status.ERROR
})
The OnSubscribe is always triggered, but after two inserts, nothing is present in db and I have no errors in my log.
Also there aren't any replace, because each time there is a new PK.
Edit
Here more information about this issue:
The weird thing is it's look like the Room Database is in a way copy in memory, when my app is looking for new records, I can have them, like (select * from entry_table), and show them to the user. But if I stop the app and restart it, the row are gone (since they aren't in the db)..
I found an answer or I could say a solution to it, but for the moment I don't understand why this behaviour changed, because I didn't change anything in my app.
By changing the JournalMode from WRITE_AHEAD_LOGGING (or automatic) to TRUNCATE, all my changes are directly performed in the DB and I don't have any weird memory DB.
I have an observable that on subscribe does a long operation but when a user click on a button I want to notify my observable to do again the long operation because something change.
I don't want to make a new subscription every time the user clicks on the button. Which is the best solution to achieve this?
I would like to know if is possible to use a solution, which use the rxjava simplified way to run code on different threads.
Should I use something like this?
BehaviourProcessor<boolean> processor = BehaviourProcessor.createDefault(true);
public Flowable<List<Item>> getItems(){
return Flowable.create(e -> e.onNext(longOp()))
.subscribeOn(Schedulers.io())
.switchMap(items -> processor.map(notify -> returnItems(notify)));
}
public void notifyChange(){
processor.onNext(true);
}
Android Room library achieve this result, in fact when you subscribe this:
#Query("SELECT * FROM user")
Flowable<List<Item>> getUsers();
Every time you delete an item from database you immediately get the new list from the database in the subscription on next method.
Rather than have getItems() return the observable chain that you have shown, return a shared observable.
Flowable<List<Item>> itemGetter =
Flowable.create(e -> e.onNext( longOp() ) )
.subscribeOn( Schedulers.io() )
.switchMap(items -> processor.map(notify -> returnItems(notify)))
.replay( 1 )
.publish();
Flowable<List<Item>> getItem() {
return itemGetter;
}
This creates only on observer chain that you can subscribe to as many times as you want. However, if there are no subscribers and another subscriber comes along, longOp() will be called again.
If you don't want that to happen, then you should consider using a BehaviorSubject<List<Item>> to cache the value.
I have Android Room's SQL query, that return flowable:
#Query("SELECT * FROM exercices WHERE lang = 'ru' AND id_exercice = :id")
Flowable<Exercices> getExercicesById(int id);
In my repository, I need get emitted element, than change his boolean value, then call new method, that must returns completable.
That's what I try:
#Override
public Completable setExerciseUsed(int id) {
return mDatabase.exerciseDao().getExercicesById(id)
.doOnNext(exercise -> exercise.setIs_used(1))
.flatMapCompletable(exercise ->
Completable.fromAction(() -> mFitnessDatabase.exerciseDao().addExercise(exercise)));
}
Also i tried:
Exercices e = mDatabase.exerciseDao().getExercicesById(id).blockingFirst();
e.setIs_used(0);
return Completable.fromAction(() -> mDatabase.exerciseDao().addExercise(e));
But t not works properly. It seems like flowable emits many elements, and it going to stuck in cycle, after subscription.
Since your DAO returns Flowable, it will emit fresh data each time table is modified.
So after calling mFitnessDatabase.exerciseDao().addExercise(exercise),
getExercicesById will emit new data, thus the chain will execute forever.
If you want Room not to emit data - just change Flowable to Single.
Since you expect the one value to e returned, it's a good idea to limit the result to one item: "SELECT * FROM exercices WHERE lang = 'ru' AND id_exercice = :id LIMIT 1".
But actually, in your case, if you want to change a parameter of an item, it's much more efficient to do this within one query.
It may look like:
#Query("UPDATE exercices SET is_used = 1 WHERE lang = 'ru' AND id_exercice = :id")
fun setIsUsed(id: Int)
I don't know much about flowables or how to unsubscribe from one source with those, but have you thought about using LiveData? You can simply add a source to a LiveData object, then remove the connection to the source, change the object and execute your method without going into loop.
Another idea would it be that you retrieve your object, keep the connection and updating the value outside of that. Because you are keeping the database connection with LiveData, the observer would execute the onChanged again (because the source = database has been changed). You only would need to make sure that this method returns for example null (via setValue) for the time being until the database uploaded the new object value.
If you want to stay with flowables, maybe you should consider adding a condition which will prevent the app from re-applying the integer to the object (and sending it to the database). Does that makes sense to you?
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