Kotlin RxJava Call API in loop one by one, terminate the loop if any API response has desire result - kotlin

I want to call API for each item in list and when the response received has desire result will return and terminate the loop.
My current code:
Observable.from(riffInPlayerSubscription)
.flatMap { item ->
API.getAccessCheck(accessToken, item.itemId)
}.flatMap { itemAccessResponse ->
if (!itemAccessResponse.isExpired()) {
credentialStore.hasActiveSubscription = true
return#flatMap Observable.just(true)
}
}
But it loops for all items and doesn't terminate.

Use skipWhile and take:
Observable.from(riffInPlayerSubscription)
.flatMap { item ->
API.getAccessCheck(accessToken, item.itemId)
}
.skipWhile { it.isExpired() }
.take(1)
.map {
credentialStore.hasActiveSubscription = true
true
}

Related

How call the same function with join at Kotlin Coroutines?

That is what i'm trying to do; first showing items with loading progress in my ui, when is the api request returned as success then i'm manipulating my list for pass to recyclerview adapter.
So i need call repeatedly updateAndSetAdapterList function in coroutines.
Here is my code that calling same function in Fragment;
lifecycleScope.launch(Dispatchers.IO) {
// Videos
viewModel.dashboardVideosFlow.collectLatest { flow ->
when (flow) {
...
is VideosFlow.DataReceived -> {
val row = SectionVideosBodyData(flow.videos)
updateAndSetAdapterList(1, row) <----
}
}
}
}
lifecycleScope.launch(Dispatchers.IO) {
// Questions
viewModel.dashboardQuestionsFlow.collectLatest { flow ->
when (flow) {
....
is QuestionsFlow.DataReceived -> {
val row = SectionQuestionsBodyData(flow.questions)
updateAndSetAdapterList(3, row) <----
}
}
}
}
And this is the function that does something first on background thread after on main thread;
private suspend fun updateAndSetAdapterList(indexAt: Int? = null, row: BaseAdapterData? = null) {
lifecycleScope.launch(Dispatchers.IO) {
val newRows = mutableListOf<BaseAdapterData>()
newRows.addAll(rows)
if (indexAt != null && row != null) {
newRows.removeAt(indexAt)
newRows.add(indexAt, row)
}
withContext(Dispatchers.Main) {
dashboardAdapter.submitList(newRows)
}
rows = newRows
}
}
I want to use synchronous api call functions but i want to use the ui update function as asynchronous that called from these synchronous functions.

Kotlin Flow two dependant request but return the first

I have two functions, the first one that returns a Result with a model, and the second one that returns a Result with another model.
fun flow1(): Flow<Result<Model1>>
fun flow2(id: String): Flow<Result<Model2>>
What i want is went the flow1() call is success, then do the flow2() call and some logic when is success but at the end return the flow1() result.
And for the moment i just trying something like this:
flow1().flatMapLatest { flow1Result ->
flow1Result.onSuccess {
flow2(it.id).map { flow2Result ->
flow2Result.onSuccess {
//Some logic
}
}
}.onFailure {
// return error
}
}
I have two problems the first one that inside the flatMapLatest give an error because say that i return a Result instead of a Flow. And how i can return the Flow1 result?
Thank you!
Trying something similar to this response Chain kotlin flows depends on Result state
I guess you need something like this
fun main() {
flow1().flatMapLatest { flow1Result ->
// should return flow with flow1Result element emited
flow1Result
.onSuccess {
// flow<flow1Result>, that we return
flow2(it.id).map { flow2Result ->
flow2Result
.onSuccess{
TODO("some logic")
}.onFailure{
// only emit flow1Result when flow2Result = Success
throw RuntimeError()
}
// convert flow<flow2Result> to flow <flow1Result>
flow1Result
}
}
.onFailure {
// there can be other flow<flow1Result>
throw RuntimeError()
}
}
}

How to invoke function based on condition of iterated value of Mono<List<String>> without using subscribe()?

I want to invoke a function that will notify the admin about some information missing, but I do not want to subscribe to this Mono, because I will subscribe to it later. The problem is I have some log which is called inside doOnSuccess() and when I use subscribe() and then build a response where I zip listOfWords value, the same log is logged twice and I do not want a code to behave that way.
Is there any way to retrieve that value in checkCondition() in a way that will not invoke doOnSuccess() or should I use some other function in merge() that can replace doOnSuccess()?
Should I use subscribe() only once on given Mono or is it allowed to use it multiple times?
Thank you in advance!
The functions are called in the presented order.
Code where log is called:
private fun merge(list1: Mono<List<String>>, list2: Mono<List<String>>) =
Flux.merge(
list1.flatMapMany { Flux.fromIterable(it) },
list2.flatMapMany { Flux.fromIterable(it) }
)
.collectList()
.doOnSuccess { LOG.debug("List of words: $it") }
Code where subscribe is called:
private fun checkCondition(
listOfWords: Mono<List<String>>,
) {
listOfWords.subscribe {
it.forEach { word ->
if (someCondition(word)) {
alarmSystem.notify("Something is missing for word {0}")
}
}
}
}
Code where response is built:
private fun buildResponse(
map: Mono<Map<String, String>>,
list1: List<SomeObject>,
listOfWords: Mono<List<String>>
): Mono<List<Answer>> {
val response = Mono.zip(map, Mono.just(list1), listOfWords)
.map { tuple ->
run {
val tupleMap = tuple.t1
val list = tuple.t2
val words = tuple.t3
list
.filter { someCondition(words) }
.map { obj -> NewObject(x,y) }
}
}

RxJava2 Maybe return empty Observable if no element

Is there a better / more idiomatic way to use a Maybe type from JavaRx 2 than flatMap and try/catch? The following example takes a Maybe<User> and tries to book them a random ticket for a flight. If the user doesn't exist, return an empty Observable.
fun bookRandomTicketFor(userId: UUID): Observable<Ticket> {
val agencies = travelAgents() // Observable<TravelAgency>
val user = findById(userId) // Maybe<User>
val location = locate() // Observable<GeoLocation>
return Observable
.just(user.toObservable())
.flatMap { usr ->
try {
usr.zipWith(location, { aUser, location ->
agencies
.flatMap { agency ->
agency
.search(aUser, location) // Observable<Flight>.
.toList() // Convert to List<Flight>.
.toObservable() // And to Observable<List<Flight>>.
.flatMap { flights -> // So it can be shuffled,
Observable.just( // giving a random order.
shuffle(flights as MutableList<Flight>)[0]
)
}
}.firstElement() // Now take the first randomly shuffled Flight.
}).flatMap { flight ->
book(user.toObservable(), flight.toObservable())
}
} catch (ex: Exception) {
Observable.empty<Ticket>()
}
}
.doOnSubscribe { Logger.log("Random ticket: start for $userId") }
.doOnComplete { Logger.log("Random ticket: exit for $userId") }
}
It seems a bit of a fudge to have to convert the Maybe<User> to an Observable and start with an Observable<Observable<User>> that I can then flatMap and try/catch. Just wondering if there is a neater approach to doing this?

How Can I Merge a Single<List<List<T>>> Into a List<T> with RxJava 2?

What I want to do is hit an endpoint to get a list of users, that returns Single<List<User>>. Next, I want to grab the first three users and hit another endpoint to get all of their posts Single<List<Post>>. Finally I want to display a Toast that has the total number of posts for all of the first 3 users.
I've been able to achieve it with the flatten() function available in Kotlin. However, I'd like to know how to do this using only RxJava 2. Is it possible? Thanks.
...
getPostsForFirstThreeUsers()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ posts -> toast("There are: ${posts.flatten().size} posts") },
{ ex -> Timber.e(ex, "there was an error processing the request") }
)
fun getFirstThreeUsers(): Flowable<User> {
return getAllUsers()
.flattenAsFlowable { users -> users }
.doOnNext { Timber.i("a user: ${it.username}") }
.take(3)
}
fun getPostsForFirstThreeUsers(): Single<List<List<Post>>> {
return getFirstThreeUsers()
.flatMapSingle { api.getUsersPosts(it.id) }
.doOnNext { Timber.i("number of posts: ${it.size}") }
.toList()
}
Since you're only looking for the total number of posts, you could just flatmap the lists to a stream and then count. The flat-mapping will not guarantee order, but flattens the list of list into a single stream.
getPostsForFirstThreeUsers()
.subscribeOn(Schedulers.io())
.flatmapObservable(Observable::fromIterable)
.flatmap(Observable::fromIterable)
.count()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ count -> toast("There are: ${count} posts") },
{ ex -> Timber.e(ex, "there was an error processing the request") }
)
I haven't tested this but I think you can do this with flatmap and reduce (or collect). probably something's like:
getAllUsers()
.take(3)
.flatMap { api.getUsersPosts(it.id) }
.map { it.size }
.reduce(0) { acc, e -> acc + e }
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ posts -> toast("There are: ${posts.flatten().size} posts") },
{ ex -> Timber.e(ex, "there was an error processing the request") }
)
I ended up going with this solution, which allows me to have access to the entire List of Post objects. Thanks for the direction.
fun getPostsForFirstThreeUsers(): Single<MutableList<Post>> {
return getFirstThreeUsers()
.flatMapSingle { api.getUsersPosts(it.id) }
.doOnNext { Timber.i("number of posts: ${it.size}") }
.flatMapIterable { it }
.doOnNext { Timber.i("the post: $it") }
.toList()
}