RxJava2 Kotlin SwitchMap from RealmResults to Observable - kotlin

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

Related

Combine a Flow and a non Flow api response Kotlin

I currently have a piece of logic as follows:
interface anotherRepository {
fun getThings(): Flow<List<String>>
}
interface repository {
suspend fun getSomeThings(): AsyncResult<SomeThings>
}
when (val result = repository.getSomeThings()) {
is AsyncResult.Success -> {
anotherRepository.getThings().collectLatest {
// update the state
}
else -> { }
}
}
The problem I am having is that, if repository.getSomeThings has been triggered multiple times before, anotherRepository.getThings is getting triggered for the amount of all the pre-loaded values from repository.getSomeThings. I was wondering what is the proper way to use these repositories, one a suspend function, the other a Flow together. The equivalent behaviour that is combineLatest{} in Rx.
Thank you.
There are a couple of ways to solve your problem. One way is just to call
repository.getSomeThings() in the collectLatest block and cache last result:
var lastResult: AsyncResult<SomeThings>? = null
anotherRepository.getThings().collectLatest {
if (lastResult == null) {
lastResult = repository.getSomeThings()
}
// use lastResult and List<String>
}
Another approach is to create a Flow, which will be calling repository.getSomeThings() function, and combine two Flows:
combine(
anotherRepository.getThings(),
flow {emit(repository.getSomeThings())}
) { result1: List<String>, result2: AsyncResult<SomeThings> ->
...
}

How to emit the result of a subscription

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

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

Vert.x Reactive Kafka client: chaining not working when writing?

I am using io.vertx.reactivex.kafka.client.producer.KafkaProducer client. The client has a
rxWrite function which returns Single<RecordMetadata>. However I need to log error if any, during write operation. It apparently is not getting executed.
I have written following working example.
test(): Function to test the chaining and logging
fun test(): Single<Int> {
val data = Single.just(ArrayList<String>().apply {
add("Hello")
add("World")
})
data.flattenAsObservable<String> { list -> list }
.flatMap { advertiser ->
//does not work with writeKafka
writeError(advertiser).toObservable().doOnError({ println("Error $data") })
}
.subscribe({ record -> println(record) }, { e -> println("Error2 $e") })
return data.map { it.size }
}
writeKafka: Writes the given given string into Kafka and returns Single
fun writeKafka(param: String): Single<RecordMetadata> {
//null topic to produce IllegalArgumentException()
val record = KafkaProducerRecord.create(null, UUID.randomUUID().toString(), param)
return kafkaProducer.rxWrite(record)
}
writeError: Always return a single with error of same type
fun writeError(param: String): Single<RecordMetadata> {
return Single.error<RecordMetadata>(IllegalArgumentException())
}
So when I call writeKafka It only prints Error2 but if I use writeError it prints both Error and Error2. Looks like the single returned by writeKafka is still waiting for result, but then why even Error2 is printed?
I am pretty newbie in RxJava2, could somebody point out any error in that?
It is important to read and post the stacktrace of errors so that the problem can be isolated.
In this case, looks like you get the IllegalArgumentException from create and you don't get any Single because the relevant Kafka class throws it. return kafkaProducer.rxWrite(record) never executes at all and you practically crash the flatMap. doOnError never gets into play hence only the "Error2" is printed.