How to emit the result of a subscription - kotlin

I have the following situation:
I am using the RxKotlin extensions for detecting clicks in the buttons of my activity
I am using Room for inserting records in a database
This is the code related in my activity:
button.clicks()
.flatMap {
val list = mutableListOf<Answer>()
val date = Date()
list.add(Answer("some placeholder info"))
list.add(Answer("Another placeholder info"))
Observable.fromArray(list)
}
.map {
upsertStatusQuestionsViewModel.insertMultipleAnswers(it)
}.subscribe {
// it here is an object Maybe<Answer>
}
And this is the code of the ViewModel:
fun insertMultipleAnswers(answers: List<Answer>) = database.answerDao()
.createMultiple(answers.toList())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
I would like to show some information about the answer inserted in the database, for that reason, I need to get the Answer object in my subscription. However, I don't know which operator can I use for achieving that the it object in the subscription is of class Answer, instead of Maybe<Answer>.
Thanks a lot for your help!

If anyone stumbles with this, the solution is to parse the Maybe to an Observable, as Observable implements ObservableSource, so my code is now something like this:
upsert_status_questions_confirm.clicks()
.map {
val list = mutableListOf<Answer>()
list.add(Answer("Some placeholder"))
list.add(Answer("Another placeholder"))
list
}.flatMap {
upsertStatusQuestionsViewModel.insertMultipleAnswers(*it.toTypedArray())
}.subscribe({
// Success...
}, {
// Error...
})
And in the ViewModel:
fun insertMultipleAnswers(vararg answers: Answer) = database.answerDao()
.createMultiple(answers.toList())
.toObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())

Related

Room + Kotlin Flow not emitting result

i'm trying to fetch some data from api, and them store on room database, so the main data source is roomDatabase.
my repository code looks like:
suspend fun fetchData(): Flow<Response<List<Foo>>> {
val shouldRequestData = dao.getFoo().isEmpty()
return if (shouldRequestData) {
getFoo()
} else getLocalFoo()
}
override suspend fun getFoo(): Flow<Response<List<Foo>>> {
return ....... request done normally... inserting normally on database (and showing
on database inspector)
}
override suspend fun getLocalFoo(): Flow<Response<List<Foo>>> = flow {
dao.getFoo().transform<List<FooLocal>, Response<List<Foo>>> {
Response.Success(
it.map {
it.toDomainModel()
}
)
}
}
on Dao:
#Query("SELECT * FROM localdb")
fun getFoo(): Flow<List<Foo>>
and then collecting it normally on viewmodel...
The problem is: the data is not appearing.. how could i solve this? The non-flow version works :/
I already searched for this problem, but nothing seems to work.
Solved by putting this on getLocalFoo() ->
val result: Flow<Response<List<Foo>>> =
Transformations.map(dao.getFoo()) {
Response.Success(it?.map {
it.asDomainModel()
} ?: emptyList()
}.asFlow()
return result
I have found a solution to investing so much time.
Solution: Same Dao Object should be used when we insert details into the room database and get information from DB.
If you are using dagger hilt then
#Singleton annotation will work.
I hope this will solve your problem.

How to add condition to method "retry" in kotlin and webflux when api return error data?

How to change the parameters with retry() in kotlin and webflux ?
There is a productInfo function, the function parameter is a collection of product ids.
When I input a wrong id in the list collection ids, the upstream interface will only return the wrong id. And get failed.
What I want to achieve is when the upstream interface returns the wrong id. The product info can remove the wrong id and have a second try with the right ids.
I tried to use retry() but I don't know how to change the parameters in the second try.
fun productInfo(ids: List<Pair<String, String>>): Flux<ProductItem> {
return productWebClient
.get()
.uri("product/items/${ids.joinToString(";") { "${it.second},${it.first}" }}")
.retrieve()
.bodyToFlux(ProductItem::class.java)
.onErrorResume {
logger.error("Fetch products failed." + it.message)
Mono.empty()
}
}
What you want is not retry(). I've built a solution making minor assumptions here and there. You can refer to this solution and make changes according to your requirements. I've used recursion here (productInfo()). You can replace the recursion call with webclient call if the error occurs only once.
fun productInfo(ids: List<Pair<String, String>>): Flux<ProductItem> {
val idsString = ids.joinToString(";") { "${it.second},${it.first}" }
return webClient
.get()
.uri("product/items/${idsString}")
.exchange()
.flatMapMany { response ->
if (response.statusCode().isError) {
response.body { clientHttpResponse, _ ->
clientHttpResponse.body.cast(String::class.java).collectList()
.flatMapMany<ProductItem> { eids ->
val ids2 = ids.filter { eids.contains("${it.second},${it.first}") }
productInfo(ids2)
}
}
} else {
response.bodyToFlux(ProductItem::class.java)
}
}
}

Simplify the statement using rxkotlin

I've wanted to try RxJava with kotlin to make coding easier, so I've produced this:
fun postAnswers() {
disposable = getToken.execute().subscribe({ token ->
questions.forEach { form ->
val answers = form.answers?.filter { it.isChecked }?.map { it.answer_id }
disposable = postAnswer.execute(token?.token!!, SavedAnswer(form.form_id, answers)).subscribe({
//Post live data about success
}, {
//Post live data failure
})
}
}, {
//Post live data failure
})
}
But I have an impression it can be done better, but I do not know how. Basically what I am trying to achieve is getting a Token object from database, that returns Flowable Token? and then use it to call postAnswer in a for cycle, because I need to post each answer separately (That's how the API is designed). After that, postAnswer only returns Completable, but I need to let the Activity know (this is from ViewModel code) how many answers were posted
I've thought about using .flatMap or .concat functions, but I am not sure if it will be helpful in this case. Also, do I need to assign getToken.execute() to disposable?
Thank you for your answers
EDIT:
Here is my questions list:
private var questions: List<Form> = emptyList()
It gets filled by viewModel functions
Try to think with nesting :) This here will probably do: for each saved answer, post a request.
disposable = getToken.execute()
.switchMap { token -> // switchMap because your old token is probably invalidated
val savedAnswers = questions
.map { form->
val formId = form.form_id
form.answers
?.filter { it.isChecked }
?.map { it.answer_id }
?.let { SavedAnswer(formId, answersIds) }
?: SavedAnswer(formId, emptyList() ) // if no checked answer, then return empty list of ids
}
Observable.list(savedAnswers)
.concatMap { savedAnswer -> // concatMap because you want the whole list to be executed once per time, use flatMap if you want it to be in parallel.
postAnswer.execute(token?.token!!, savedAnswer) // FYI: !! is bad practice in Kotlin, try make it less anbiguous
}
.toList()
}
.subscribe({ listOfResultsFromPostings : List<SomeResultHere> ->
//Post live data about success
}, {
//Post live data failure
})

RxJava2 Kotlin SwitchMap from RealmResults to Observable

OK this is my first question in RxJava so please be gentle.
I'm querying Realm for existing users, getting a RealmResults list back as a flowable, then I would like to either create a new user or return the existing user, then convert to JSON.
This is what I have so far. I'm a bit stuck.
fun getUsers(realm: Realm): Flowable<RealmResults<User>> {
return when (realm.isAutoRefresh) {
true -> realm.where<User>().findAllAsync().asFlowable().filter(RealmResults<User>::isLoaded)
false -> Flowable.just(realm.where<User>().findAll())
}
}
fun checkNewUserRequired(realm: Realm, results: RealmResults<User>): Observable<String> {
if (results.isEmpty()) {
//not complete, I will create a new user here
return Observable.just("Dummy")
} else {
val user = realm.where<User>().findFirst()!!
val detachedUser = realm.copyFromRealm(user)
return Observable.just(userToJsonString(realm, detachedUser))
}
}
val getNewUser= getUsers(realm)
.take(1)
.switchMap{ results -> checkNewUserRequired(realm, results) }
.subscribe{
//log result
result : String -> Log.d(TAG, "JSON OUTPUT: $result")
}
The error is on the switchmap. I'm very familiar with the operator in RxJS but I'm struggling with the syntax.
Any help much appreciated.
You are trying to switchMap an Flowable into an Observable, which are actually different types. You need to convert from one type to the other.
The easiest solution in your case, since it looks like you will not have any issues related to Back pressure, is to convert checkNewUserRequired to return a Flowable
Example
fun checkNewUserRequired(realm: Realm, results: RealmResults<User>): Flowable<String> = Flowable.just(
if (results.isEmpty()) "Dummy"
else {
val user = realm.where<User>().findFirst()!!
val detachedUser = realm.copyFromRealm(user)
userToJsonString(realm, detachedUser)
}
)
You can also convert from an existing Observable to Flowable using the function toFlowable, but then you need to specify a BackpressureStrategy.
Example
.switchMap{ results -> checkNewUserRequired(realm, results).toFlowable(BackpressureStrategy.DROP) }

MediatorLiveData vs LiveData

I am not able to get the idea of MediatorLiveData while working with LiveData. In documentation it is mentioned that MediatorLiveData is a subclass of LiveData that will observe LiveData and react on onChanged method. My question is what's it's difference from having a function in observe of LiveData and do something with data if the new data is different?
For example, let's say I have query from Room Database that returns LiveData as below
#Query(“SELECT * FROM Users WHERE userid = :id”)
fun getUserById(id: String): LiveData<User>
If I want to use MediatorLiveData, I can have an extension as below
fun <T> LiveData<T>.distinctUntilChanged(): LiveData<T>{
var lastValue: Any? = Any()
return MediatorLiveData<T>().apply {
addSource(this#distinctUntilChanged){
if (it != lastValue){
lastValue = it
postValue(it)
}
}
}
}
And then use it in my Activity as
userDao.getUserById("someId").distinctUntilChanged()
.observe(this, user -> {/*Do something with user*/})
Contrary to above scenario, I can just have a wrapper function inside observe method as below
var lastUser = null
userDao.getUserById("someId")
.observe(this, user -> {
if (lastUser != user){
lastUser = user
/*Do something with user*/
}
})
Can anyone elaborate why I should use MediatorLiveData instead of just having simple check?