1

Using RxJava2 RxKotlin and Room, I need to query the database for an open hunt. That means that I search for a hunt that contains an attribute called closed with value false. Once the hunt has been found, it needs to switch the query to that particular hunt.

I have 2 methods for those queries:

getOpenHunt(teamId:String): Flowable<List<Hunt>>
getHunt(huntId:String): Flowable<List<Hunt>>

They both return a List because otherwise the query gets stuck when no hunt is found.

My idea is something like

fun queryHunt(teamId:String):Flowable<Optional<Hunt>>{
   getOpenHunt(teamId)
      .map<Optional<Hunt>> {
                Optional.create(it.firstOrNull())
            }
      .switchToFlowableIf ( it is Optional.Some, getHunt(it.element().id)
}

//With switchToFlowableIf's being
fun <E:Any> switchToFlowableIf(condition: (E)->Boolean, newFlowable: Flowable<E>): Flowable<E>
//It should unsubscribe from getOpenHunt and subscribe to newFlowable

For reference, here is my Optional class

sealed class Optional<out T> {
class Some<out T>(val element: T) : Optional<T>()
object None : Optional<Nothing>()

fun element(): T? {
    return when (this) {
        is Optional.None -> null
        is Optional.Some -> element
    }
}

companion object {
    fun <T> create(element: T?): Optional<T> {
        return if (element != null) {
            Optional.Some(element)
        } else {
            Optional.None
        }
    }
}
}

Is there a similar method already built in RxJava2? If not, how would you implement it?

David Corsalini
  • 7,958
  • 8
  • 41
  • 66

2 Answers2

1

I solved this one by looking at onErrorResumeNext. Copy pasted some code and modified to my needs. I don't think this is perfect, but it does his work. Comment if you find some possible errors.

public final class FlowableOnPredicateNext<T> extends AbstractFlowableWithUpstream<T, T> {

private final Predicate<? super T>                                  predicate;
private final Function<? super T, ? extends Publisher<? extends T>> next;

public FlowableOnPredicateNext(Flowable<T> source, Predicate<? super T> predicate,
                               Function<? super T, ? extends Publisher<? extends T>> next) {
    super(source);
    this.predicate = predicate;
    this.next = next;
}

@Override
protected void subscribeActual(Subscriber<? super T> s) {
    OnPredicateNextSubscriber<T> parent = new OnPredicateNextSubscriber<>(s, next, predicate);
    s.onSubscribe(parent.arbiter);
    source.subscribe(parent);
}

static final class OnPredicateNextSubscriber<T> implements FlowableSubscriber<T> {

    private final Subscriber<? super T>                                 actual;
    private final Predicate<? super T>                                  predicate;
    private final SubscriptionArbiter                                   arbiter;
    private final Function<? super T, ? extends Publisher<? extends T>> nextSupplier;
    private boolean switched = false;

    OnPredicateNextSubscriber(Subscriber<? super T> actual,
                              Function<? super T, ? extends Publisher<? extends T>>
                                      nextSupplier, Predicate<? super T> predicate) {
        this.actual = actual;
        this.predicate = predicate;
        this.nextSupplier = nextSupplier;
        this.arbiter = new SubscriptionArbiter();
    }

    @Override
    public void onSubscribe(Subscription s) {
        arbiter.setSubscription(s);
    }

    @Override
    public void onNext(T t) {
        try {
            if (!switched && predicate.test(t)) {
                Publisher<? extends T> p;
                p = nextSupplier.apply(t);
                p.subscribe(this);
                switched = true;
            } else {
                actual.onNext(t);
            }
        } catch (Exception e) {
            actual.onError(e);
        }
    }

    @Override
    public void onError(Throwable t) {
        actual.onError(t);
    }

    @Override
    public void onComplete() {
        actual.onComplete();
    }
}
}

Using Kotlin I wrote an extension function:

@CheckReturnValue
@BackpressureSupport(BackpressureKind.FULL)
@SchedulerSupport(SchedulerSupport.NONE)
fun <E, T : Flowable<E>> T.onPredicateResumeNext(predicate: Predicate<E>, resumeFunction: io.reactivex.functions.Function<E, Publisher<E>>): Flowable<E> {
return RxJavaPlugins.onAssembly<E>(FlowableOnPredicateNext<E>(this,
                                                              predicate,
                                                              resumeFunction
                                                             ))
}

And I'm now using it like this:

override fun getOpenHunt(teamId: String): Flowable<Optional<Hunt>> {
    return created().getOpenHunt(teamId)
            .map {
                Optional.create(it.firstOrNull())
            }
            .onPredicateResumeNext(predicate = Predicate { it.element() != null },
                                   resumeFunction = Function {
                                       created()
                                               .getHunt(it.element()!!.id)
                                               .map<Optional<Hunt>> {
                                                   Optional.create(it.firstOrNull())
                                               }
                                   })
}
David Corsalini
  • 7,958
  • 8
  • 41
  • 66
  • Without validating for a better solution since im currently not on the computer it sounds interesting, upvoted your solution. May be great if you can also extend your question with a very simple uml-diagram – Emanuel Oct 02 '17 at 13:35
0

I would do it that way by reusing your methods:

getOpenHunt(teamId:String): Flowable<List<Hunt>>
getHunt(huntId:String): Flowable<List<Hunt>>

getOpenHunt(yourId) // return your Flowable<List<Hunt>>
.flatMapIterable { it } // get each result of open hunts
.flatMapMaybe {  getHunt(it.id) } // querie each result 
.toList() // back to a Maybe/Single<List<Hunt>>
.toFlowable() // back to a Flowable else it wont emit more data
.subscribeBy(onNext = { /** process your results **/ },
             onError = { /** process if no results found **/  }
            )

That means you query your Database if there is an entry which matches your condition. Then it get the result, split it into single items, run several queries using your results. If there are results found it converts it back to a Single<List<Hunt>>. Since you want to keep subscribed you need to convert it back to a Flowable using .toFlowable()

By the way, change means that the return value changes, but in your case it's the same List<Hunt>.

The whole flow doesnt make any sense for me since you could also get the results by getOpenHunt and iterate through it.

Emanuel
  • 8,027
  • 2
  • 37
  • 56
  • This would not work: getOpenHunt shouldn't find an openHunt on it's first run, because the hunt may not exist yet and this isn't an error (so throwing an exception is not the way to go). Then the hunt is created, so getOpenHunt.onNext should be called. At that point getOpenHunt should be unsubscribed because this hunt will eventually reach a point of not being open, so getOpenHunt.onNext would be called again (improper behavior). Hunt's lifecycle: Non Existent -> Open -> Closed – David Corsalini Oct 02 '17 at 10:32
  • Hold on, let me understand your question to update my answer. Edit: Do you mean that you want to create a hunt if there's no result and in case if there's a result you want to return it using getHunt? – Emanuel Oct 02 '17 at 10:33
  • No, the hunt is created by another method. This one is just observing the database via 2 queries: getOpenHunt to find the open hunt, getHunt when an open hunt is found. Once it finds the open hunt, I want to unsubscribe from getOpenHunt and subscribe to getHunt (using the huntId retrieved by getOpenHunt). – David Corsalini Oct 02 '17 at 10:37
  • Thats exaclty what my sample does. Querie for openHunt, requires to find at least one result. If one result is found it "unsubscribe" from getOpenHunt, queries getHunt with the result and require another result. – Emanuel Oct 02 '17 at 10:40
  • firstOrError means that it query the db once, if no open hunts are found, it throws an Exception, which means the flowable is completed and it will not listen for db changes. If it find the open hunt on the first run, it switches to getHunt, which should be converted to a single (firstOrError return a Single) and this means I won't get updates when the db is updated. In conclusion this is not a solution to my problem, but thanks anyway. – David Corsalini Oct 02 '17 at 10:45
  • Now i got what you want. Ill update my answer, hold on – Emanuel Oct 02 '17 at 10:45
  • With your edit I would get a new empty list when the hunt goes from open to closed, because getOpenHunt is still subscribed. I'm not familiar with flatMapMaybe, what happens when the value is null? Does it skip the {} block or it does not emit at all? – David Corsalini Oct 02 '17 at 10:59
  • If you have a query which has a filter, it only returns if values are found. No empty list is returned. flatMapMaybe is like a single but it is not requires to have a result. It will only emit if there are data. That means no empty data are emitted. If you want to make sure that the first query doesnt contain empty data you can use .filter{it.isEmpty()} after the first query. – Emanuel Oct 02 '17 at 11:02
  • Then this is not a solution for me. I need to know if the hunt is null, open or closed, queries stuck when no items are found are not a solution. – David Corsalini Oct 02 '17 at 11:11