Let's say I have a flowable, that some view is subscribed to and it's listening to the changes. I would like to add a custom method based on only the first emit of the flowable, but also keeping the other methods that listen to the changes. What is the best way to approach it?
The naive approach I have is to duplicate the flowable and convert it to Single or Completable to get the results, but it seems redundant.
Thank you.
Use .take(1). BTW also make sure that flowable is shared (otherwise some observers will miss events).
I think you can use share operator for that. Share operator makes a Connectable Observable. And then Connectable Observable publishes items each subscribes.
val o = Flowable.fromArray(1, 2, 3, 4, 5)
.map {
println("heavy operation")
it + it
}
.share() // publish the changes
.subscribeOn(Schedulers.computation()) // for testing. change what you want
o.take(1).subscribe { println("Special work: $it") } // take one
o.subscribe { println("Normal work: $it") }
Result
heavy operation
Special work: 2
Normal work: 2
heavy operation
Normal work: 4
heavy operation
Normal work: 6
heavy operation
Normal work: 8
heavy operation
Normal work: 10
Related
Background:
I have an observable that returns mock items and actual items. Mock items are used in ui while actual items are still on the way. Mock items should always arrive first to prevent accidental override of actual items in ui. The premise is that ui layer is dumb, and it does not know which items it is showing nor it's managing them in any way. So the single source of items is this observable. Simplified it looks like this.
val one = Observable.fromCallable { "Mock/cached items" }
val two = Observable.fromCallable { "Live items" }
val result = Observable.concat(listOf(one, two))
.subscribe {
println(it)
}
Now I want to replay this observable, because further downstream it's just one observable of many that are combined together via combineLatest() operator, hence I can't resubscribe to it individually. So, I've added replayWhen(...) after concat() and everything worked properly.
Problem:
Things got tricky when I needed to replay only mock items in some cases vs replaying all items in other cases. Attempt that failed:
val subject1 = PublishSubject.create<Unit>()
val subject2 = PublishSubject.create<Unit>()
val one = Observable.fromCallable { "Mock/cached items" }.repeatWhen { subject1 }
val two = Observable.fromCallable { "Live items" }.repeatWhen { subject2 }
val result = Observable.concat(listOf(one, two))
.subscribe {
println(it)
}
subject1.onNext(Unit)
subject2.onNext(Unit)
Obviously, it prints only mock items while live items aren't called (anytime soon:-)). This is because concat waits for onComplete from first observable, that doesn't happen because of replay operator.
Solutions, and their drawbacks:
Replace concat with merge. This way both observable would work, but there's no guarantee that observable one would fire before the observable two. Is there any way to achieve ordering between observables without concat?
Have replayWhen() after concat, to replay this result observable as a whole, but have something inside .fromCallable{} of observable two to know when to skip it. Not sure what could it be, as a simple flag would be unreliable and dependant on external state that may change sporadically.
Thank you for any other ideas.
I'm trying for some time now to implement an extension function (just becuse it's easier to me) that is capable of delaying both normal item emissions and errors. The existing delay operators only delays normal item emissions, errors are delivered ASAP.
For context, I'm trying to immitate an Android LiveData's behavior (kinda). LiveDatas are a observable pattern implementation that is lifecycle aware. Their observers are only notified if they are in a state where they can process that emission. If they are not ready, the emission is cached in the livedata and delivered as soon as they become ready.
I created a BehaviourSubject that emits the state of my Activities and Fragments when it changes. With that I created a delay operator like this:
fun <T> Flowable<T>.delayUntilActive(): Flowable<T> = delay { lifecycleSubject.toFlowable(BackpressureStrategy.LATEST).filter { it.isActive } }
and then use it like this
myUseCase.getFlowable(Unit)
.map { it.map { it.toDisplayModel() } }
.delayUntilActive()
.subscribe({
view.displaySomethings(
}, { }).addTo(disposables)
So even if myUseCase emits when the view is not ready to display somethings, the emission won't reach onNext() until the view does become ready. The problem is that I also want the view to displayError() when onError is triggered, but that too is lifecycle sensitive. If the view isn't ready, the app will crash.
So I'm looking for a way to delay both emissions and errors (onComplete would be good too). Is this possible?
I tried some things with zip, onErrorReturn, delay inside delay, but nothing seemed right. I'd be equally unimpressed if this had a really easy solution I'm overlooking, or is impossible. Any ideas are welcome.
Bonus: any better way to do that for Single and Completable too? currently I'm just converting them to flowable.
Thanks in advance!
You can handle the error via onErrorResumeNext, then taking the same error and delaying it via delaySubscription until your desired signal to emit said error happens:
source
.onErrorResumeNext({ error ->
Observable.error(error)
.delaySubscription(lifecycleSubject.filter { it.Active } )
})
I have been working with Rx Java 2 for awhile but recently came across a situation that has stumped me. I have a semi-complex chain of operations and wish to pass a "state object" down the chain.
There are 4 operations during which I wish to repeat operations 2 and 3 (serialy, not together) until certain conditions are true. I know i can solve this by chaining each operation using andThen(), but this limits my ability to pass a state object down the chain without reaching outside of the chain.
The reason I need to have a state object is because I need to save an initial value during the first operation and compare it to a value recieved during operation 4 to determine if the overall procedure was successful.
Any clues as to what RxJava2 operators can help me achieve the proper repeat conditions for operation 2 and 3? I would prefer to not nest observables if possible.
You can keep your state as some AtomicReference<State> and use repeatUntil operator.
AtomicReference<State> state = new AtomicReference<>();
Completable operation = Completable.create() // do something and modify state
.repeatUntil(() -> state.get() == SATISFYING_CONDITION);
You can easily chain these Completables with andThen
I am trying to transform Observable using concatMap, since the order is important for my case.
#Test
fun load_data() {
val sub = TestSubscriber<Long>()
var s = BehaviorSubject.create<Long>()
s.concatMap {
Observable.timer(it, TimeUnit.MILLISECONDS)
}
.take(4)
.subscribe(sub)
s.onNext(5)
s.onNext(6)
s.onNext(7)
s.onNext(8) //rx.exceptions.MissingBackpressureException
sub.awaitTerminalEvent(500, TimeUnit.MILLISECONDS)
sub.assertNoErrors()
}
I have changed real data loading to Observable.timer() in order to simplify example and make it easier to reproduce.
I am using in the app BehaviorSubject to link UI actions with rx
From documentation, especially from marble diagram I expect that it will store items in queue and transform them one-by-one.
However it seems like concatMap has queue with size set only to 2 items. Adding more items cause MissingBackpressureException
So I have following questions:
Why concatMap has queue size 2 instead of RxRingBuffer.SIZE as
other operators has?
Should I as a rule add any of onBackpressure* operators before
calling concatMap to prevent from MissingBackpressureException
exception?
Before I answer the questions, please consider switching to RxJava 2 where this is be not a problem with an Observable.
Why concatMap has queue size 2 instead of RxRingBuffer.SIZE as other operators has?
The operator runs one Observable at a time and there was no reason to prefetch more than 1 in advance.
Should I as a rule add any of onBackpressure* operators before calling concatMap to prevent from MissingBackpressureException exception?
Yes.
I have a button which when pressed should make the btnSubject's onNext fire and make an API call in an Observable created in my ViewModel like so:
val apiObservable =
btnSubject.flatMap{ apiService.getSomething()
.toResponseOrErrorObservable()
.subscribeOn(Schedulers.io())}
Then I can reuse this observable to create two more, which are then subscribed to from my view allowing me to keep the logic in my ViewModel like so:
val successObservable = apiObservable.filter{ it.isSuccess() }
val failureObservable = apiObservable.filter{ it.isFailure() }
So apiObservable is triggered by the btnSubject.onNext() being called.
The view is then updated because it's listening to the successObservable and failureObservable
Is this possible? Perhaps with a .share() on the apiObservable?
UPDATE
I added the share operator and all observables emitted items when first subscribing. Even the filters didn't stop it... I must be missing something obvious
There might be a few way to do that.
As you have written, using share() operator multiplies output to many Subscribers. However, you have to be careful, that you also have to call connect() to turn cold Observable into hot one. If calling also replay(), you woudln't need to call connect() many times.
(Source)
However, there is more simple solution: use Jake Wharton's library RxReplayingShare. The author of previous blog post suggests it in his next article