How can I read a flowable list of values from room and convert it to another object which is a combination of more values from room
database.leadsDao().getLeads(leadState.name)
.flatMap {
val len = it.size.toLong()
Flowable.fromIterable(it)
.flatMap {
Flowable.zip(
database.orderDao().getById(it.orderId),
database.orderMedicineDao().getByOrderId(it.orderId),
database.patientDao().getById(it.patientId),
Function3<Order, List<OrderMedicine>, Patient, LeadDetail>
{ order, orderMedicines, patient -> LeadDetail.from(it, patient, order, orderMedicines) })
}
.take(len)
.toList()
.toFlowable()
}
The code above works but I don't like the take(len) part. And without it, the stream never calls onNext of the subscriber. The stream keeps waiting for more items, which shouldn't happen since Flowable.fromIterable gives finite number or items and then ends. I.e., the code below doesn't work
database.leadsDao().getLeads(leadState.name)
.flatMap {
Flowable.fromIterable(it)
.flatMap {
Flowable.zip(
database.orderDao().getById(it.orderId),
database.orderMedicineDao().getByOrderId(it.orderId),
database.patientDao().getById(it.patientId),
Function3<Order, List<OrderMedicine>, Patient, LeadDetail>
{ order, orderMedicines, patient -> LeadDetail.from(it, patient, order, orderMedicines) })
}
.toList()
.toFlowable()
}
Flowable.fromIterable gives finite number or items and then ends.
But the Flowable.zip inside of the flatmap will not end, since Room's DAO objects emit the current value AND all future updates, so the database.*() calls that are zipped together are not finite. If you add a .first() call to the inner Flowable.zip the second version should work as well.
Related
I have got the following method:
operator fun invoke(query: String): Flow<MutableList<JobDomainModel>> = flow {
val jobDomainModelList = mutableListOf<JobDomainModel>()
jobListingRepository.searchJobs(sanitizeSearchQuery(query))
.collect { jobEntityList: List<JobEntity> ->
for (jobEntity in jobEntityList) {
categoriesRepository.getCategoryById(jobEntity.categoryId)
.collect { categoryEntity ->
if (categoryEntity.categoryId == jobEntity.categoryId) {
jobDomainModelList.add(jobEntity.toDomainModel(categoryEntity))
}
}
}
emit(jobDomainModelList)
}
}
It searches in a repository calling the search method that returns a Flow<List<JobEntity>>. Then for every JobEntity in the flow, I need to fetch from the DB the category to which that job belongs. Once I have that category and the job, I can convert the job to a domain model object (JobDomainModel) and add it to a list, which will be returned in a flow as the return object of the method.
The problem I'm having is that nothing is ever emitted. I'm not sure if I'm missing something from working with flows in Kotlin, but I don't fetch the category by ID (categoriesRepository.getCategoryById(jobEntity.categoryId)) it then works fine and the list is emitted.
Thanks a lot in advance!
I think the problem is that you're collecting infinite length Flows, so collect never returns. You should use .take(1) to get a finite Flow before collecting it, or use first().
The Flows returned by your DAO are infinite length. The first value is the first query made, but the Flow will continue forever until cancelled. Each item in the Flow is a new query made when the contents of the database change.
Something like this:
operator fun invoke(query: String): Flow<MutableList<JobDomainModel>> =
jobListingRepository.searchJobs(sanitizeSearchQuery(query))
.map { jobEntityList: List<JobEntity> ->
jobEntityList.mapNotNull { jobEntity ->
categoriesRepository.getCategoryById(jobEntity.categoryId)
.first()
.takeIf { it.categoryId == jobEntity.categoryId }
}
}
Alternatively, in your DAO you could make a suspend function version of getCategoryById() that simply returns the list.
Get an idea from the code below if your Kotlin coroutine flow gets lost with a continuation approximate peak alloc exception
fun test(obj1: Object,obj2: Object) = flow {
emit(if (obj1 != null) repository.postObj(obj1).first() else IgnoreObjResponse)
}.map { Pair(it, repository.postObj(obj2).first()) }
I am using RxJava to get list of Posts from JSONplaceholder api.
https://jsonplaceholder.typicode.com/posts
I want to take only the top 10 from the list and save in the data base.
I am aware I need to use take operator but cannot figure out how to use that with concatMap.
Here is what I have already.
private fun loadPosts(){
subscription = Observable.fromCallable { postDao.all }
.concatMap { dbPostList ->
postApi.getPosts().concatMap { apiPostList ->
//HERE i ONLY WANT TO TAKE 10 ITEMS AND SAVE IT (HOW CAN I USE TAKE OPERATOR HERE)
postDao.insertAll(*apiPostList.toTypedArray())
Observable.just(apiPostList)
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { onRetrievePostListStart() }
.doOnTerminate { onRetrievePostListFinish() }
.subscribe(
{ result -> onRetrievePostListSuccess(result) },
{ onRetrievePostListError() }
)
}
Below code I tried and it does not work as expected.
postApi.getPosts()
.take(10) // DOES NOT WORK
.concatMap { apiPostList ->
postDao.insertAll(*apiPostList.toTypedArray())
Observable.just(apiPostList)
}
getPosts() returns a list. To use take(10) in your case you'd have to emit each element of the list individual. However, since you emit the entire list in one go, it is as if take(10) is trying to take 10 lists of posts rather than 10 posts.
I can think of 2 ways to fix this. You can convert the list to and observable like:
postApi.getPosts()
.flatMap { Observable.fromIterable(it) }
.take(10)
.toList()
Emit each item of the list, take 10 of them and collect the results in a list ready for your concatMap.
Another option is to manually slice the list:
postApi.getPosts()
.map { it.slice(0 until 10) }
Not so rx-ish but still should work.
Careful because both approaches assume there are at least 10 items in the list.
I'm confused about how to implement this in RxJava.
I would like to
take an object from my database
upload it
delete it from the database
take the next item from the database and repeat 2 and 3
complete when the database has no objects remaining
I know how to do this via loading all objects from the database at first and creating an Observable like this Observable.fromIterable(allMyDbObjects), however I would like to take objects one at a time, in case the database is updated while I'm uploading.
I can't figure out how to do this. I've looked at repeatUntil but it just seems to repeat instantly. Here is pseudocode for what I'm thinking:
getFirstDbObject()
.flatMapCompletable { obj ->
upload(obj)
.doOnComplete {
deleteFromDb(obj)
}
}
.repeatUntil {
// dbIsEmptyLogic.
// This doesn't work. I need to somehow call getFirstDbObject() again
}
Can anyone help?
Assuming getFirstDbObject() returns a Maybe, you can achieve this by mapping the result to a boolean (true if the database is empty, false if not) and then repeating the sequence until getFirstDbObject() returns empty and the stream completes.
getFirstDbObject()
.toObservable()
.flatMapSingle { obj ->
upload(obj)
.doOnComplete { deleteFromDb(obj) } // probably better to use .andThen(deleteFromDb(obj)) if delete also returns a completable
.toSingleDefault(false)
}
.defaultIfEmpty(true)
.repeat()
.takeUntil { isComplete ->
isComplete
}
This is a working solution in my code base.
val source = HashSet<String>()
source.add("a")
source.add("b")
source.add("c")
source.add("d")
source.add("e")
io.reactivex.Observable.just(Unit)
.flatMap { it ->
io.reactivex.Observable.fromCallable {
println("first flatmap print $it")
// uploadObj()
source.first()
}
}.flatMap {
// delete
io.reactivex.Observable.fromCallable {
source.remove(it)
println("second flatmap remove $it")
// delete object
}
}
.repeatUntil { source.isEmpty() }
.subscribe()
I want to implement method to edit a note, save it to local database (cache) and then send it to the server as a POST request. I am learning RxJava and I wanted to create Observable from the note and then apply transformations on it, like to map it to an Entity model and saving. The issue that my method returns Completable and this chain returns Observable<Completable>. How to unwrap the Completable from this Observable which I used only to start RxJava stuff. Each editNote() methods returns a Completable.
override fun editNote(note: Note): Completable {
return Observable.just(note)
.map { mapper.mapToEntity(it) }
.map { noteEntity ->
factory.getCacheDataStore().editNote(noteEntity)
.andThen { factory.getRemoteDataStore().editNote(noteEntity) }
}
}
=======================================================
UPDATE
Finally, I managed to find "a solution" but I am not sure it is correct :-)
override fun editNote(note: Note): Completable {
return Observable.just(note)
.map { mapper.mapToEntity(it) }
.flatMapCompletable { noteEntity ->
factory.getCacheDataStore().editNote(noteEntity)
.andThen { factory.getRemoteDataStore().editNote(noteEntity) }
}
}
You're looking for flatMapCompletable instead of map, because map just intercepts the stream and maps the emissions to another type, while 'flatMap' (or it's siblings), from the docs:
Transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable.
You can see it's marble diagram in Here
I am working on a feature where I need to filter out the network response data based on local database data.
For example, my network layers return me a list of items and my database layer returns an observable list of ids. Now I want to only return those objects from network layer whose id matches anyone from the database layer response.
Below code fetches data from the network and saves the result to a database(cache).
factory.getRemoteDataStore()
.searchForVenues(query)
.toObservable()
.distinctUntilChanged()
.flatMap { venues ->
factory.getCacheDataStore()
.saveVenues(venues)
.andThen(Observable.just(venues))
}
I also have a method that returns a list of venues that needs to be filtered
factory.getCacheDataStore().getDislikedVenues()
Now, how do I extend the previous chain to use getDislikedVenues() Observable to filter them from the response of network response?
Sorry for such noob question, I really am struggling with this.
One way of doing this is
factory.getCacheDataStore().getDislikedVenues()
.flatMap { dislikedItems ->
factory.getRemoteDataStore()
.searchForVenues(query)
.toObservable()
.distinctUntilChanged()
.flatMapIterable { it }
.filter { !dislikedItems.contains(it.id) }
.toList()
.toObservable()
}
.flatMap { venues ->
factory.getCacheDataStore()
.saveVenues(venues)
.andThen(Observable.just(venues))
}
P.S.: As I understand, factory.getRemoteDataStore().searchForVenues(query) returns Single or Maybe. In that case distinctUntilChanged() don't work because it relies on multiple emission (onNext()), but Single or Maybe can emit only one result.