4

I'm looking for a cleaner way on how to create a conditional flatMap(), I've read this but I'm having trouble applying it on my code:

// given variables for the sake of simplicity
val stringSingle = Single.just("dog")
val isCatEat = Single.just(true)
val feedCat = Single.just(true)

// example
stringSingle
   .flatMap { string -> 
      if (string == "cat") {
         return@flatMap isCatEat()
             .flatMap { isCatEat ->
                if (isCatEat) { // if cat already ate, proceed immediately
                    Single.fromCallable { true }
                } else { // if not, feed cat
                   feedCat()
                }
             }
      } else {
         Single.fromCallable { false }
      }
   }

as you can see (well, the code is very ugly, nesting ugh), I want to avoid calling the feedCat() by checking it first if the cat already ate. I'm having trouble applying compose() function as I can't reproduce my condition.

Tenten Ponce
  • 2,436
  • 1
  • 13
  • 40

2 Answers2

3

You can use filter, which will only emit if the predicate is satisfied.

I've assumed you want to know when this cat needs fed. Therefore, I think an observable would be more suitable:

private val hasCatEaten = Single.just(true)

fun feedCat(animal: String): Observable<Unit> =
        Observable.just(animal)
                .filter { it == "cat" }
                .flatMapSingle { hasCatEaten }
                .filter { !it }
                .map { Unit }

fun observeFeedCat() {
    feedCat("cat")
            .subscribeOn(schedulers.ioScheduler())
            .observeOn(schedulers.mainScheduler())
            .subscribeBy(onNext = { // Called when cat needs to be fed })
            .addTo(disposables)
}

UPDATE

This is a better solution, which handles both cases:

    fun shouldFeed(animal: String): Single<Boolean> =
        Single.fromCallable { animal }
                .filter { it == "cat" }
                .flatMap { Maybe.fromCallable { !hasEaten } }
                .toSingle(false)

I unit tested this code (not cat, cat has eaten and cat not eaten) so Im pretty confident with this answer.

Iain
  • 371
  • 1
  • 3
  • 10
  • Sorry, but I also want to know if the cat is not yet eating. In other words, I don't want only to handle if the condition is satisfied, I also want to handle if it doesn't. – Tenten Ponce Apr 12 '19 at 01:15
  • Ok no worries. See the update above. Let me know how you get on. – Iain Apr 12 '19 at 10:10
  • I've used the extension solution from Boris (see above), but thanks for this, I'll also try this if I encountered another problem like that. – Tenten Ponce Apr 15 '19 at 05:53
  • It's unnecessary to have those extension functions. Why do that when Rx already provides the necessary operators? My solution is much cleaner. – Iain Apr 15 '19 at 06:28
  • Hmm okay, I'll try this first, I'll get you back, refactoring takes some time. – Tenten Ponce Apr 15 '19 at 06:32
  • It seems that I can't use this, because `hasEaten` is Single, which returns true or false. And also, once you use filter, it will only emit if true. Maybe I'm just a bit confused, if you can use my given variables with your solution, maybe I can integrate it. – Tenten Ponce Apr 23 '19 at 07:26
1

I'd extracted the second if into extended function

// given variables for the sake of simplicity
val stringSingle = Single.just("dog")
val isCatEat = Single.just(true)
val feedCat = Single.just(true)

// example
stringSingle
        .flatMap { string ->
            if (string == "cat") {
                isCatEat.flatMapIfTrue { feedCat }
            } else {
                Single.fromCallable { false }
            }
        }

Where:

fun Single<Boolean>.flatMapIfTrue(mapper: (Boolean) -> Single<Boolean>): Single<Boolean> =
        this.flatMapIf({ it }, mapper)

fun Single<Boolean>.flatMapIfFalse(mapper: (Boolean) -> Single<Boolean>): Single<Boolean> =
        this.flatMapIf({ !it }, mapper)

fun <T> Single<T>.flatMapIf(conditions: (T) -> Boolean, mapper: (T) -> Single<T>): Single<T> =
        this.flatMap {
            if (conditions(it)) mapper(it)
            else Single.just(it)
        }

I provided 3 funs so you can reuse it in other places

borichellow
  • 1,003
  • 11
  • 11
  • From what I seeing, you've moved the process on another function. It is much cleaner in this way. But I'll still wait for another answer that might use the existing operators on rxjava. – Tenten Ponce Apr 12 '19 at 01:13
  • Just wanted to note that this solution will only work if the `Singles` used by `flatMapIf` have the same item type (`T`), because the mapper doesn't return a different result type (`R`) like RxJava's `flatMap` method does. – starkej2 Jan 20 '22 at 02:55