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
Related
I wrote the code as below.
suspend fun getDataByRegion(): Flow<Result?> {
// getRegion() return Flow<Region>
return getRegion().map { region: Region ->
repository.requestDataFromServer(region)
}
}
The problem is that repository.requestDataFromServer(region) called twice.
So I think I should use operators like zip or combine.
When using these operators, how can the second flow use the data of the first flow?
With combine and zip operators you can not depend on the other's result. So in general your chaining approach with map is OK.
There is several options you have:
Assuming your repository method is not called from anywhere else, the reason for it being called twice is that the region Flow is emitting twice. So try to find out why this is the case.
Anyhow if your region Flow method returns the same region twice you can fix it by simply adding
.distinctUntilChanged() after getRegion() like:
getRegion().distinctUntilChanged().map { region: Region ->
repository.requestDataFromServer(region)
}
It will make sure your region Flow doesn't emit redundantly with the same data. Alternatively add distinctUntilChanged() directly to the repository method, if this is always the expected behavior.
Ask yourself if this method really needs to return a stream (Flow). I guess you need a stream since the region can change at runtime and you want something in your app to update automatically? But if not you could simply convert the stream to a single result:
val region = getRegion().first()
repository.requestDataFromServer(region)
I tried using Kotlin Flow to be some kind of message container which should pass this message to all observers (collectors). I do not want to use LiveData on purpose because it need to be aware of lifecycle.
Unfortunately I have noticed that if one collector collects message from flow no one else can receive it.
What could I use to achieve "one input - many output".
You can use StateFlow or SharedFlow, they are Flow APIs that enable flows to optimally emit state updates and emit values to multiple consumers.
From the documentation, available here:
StateFlow: is a state-holder observable flow that emits the current and new state updates to its collectors. The current state value can also be read through its value property.
SharedFlow: a hot flow that emits values to all consumers that collect from it. A SharedFlow is a highly-configurable generalization of StateFlow.
A simple example using state flow with view model:
class myViewModel() : ViewModel() {
val messageStateFlow = MutableStateFlow("My inicial awesome message")
}
You can emit a new value using some scope:
yourScope.launch {
messageStateFlow.emit("My new awesome message")
}
You can collect a value using some scope:
yourScope.launch {
messageStateFlow.collect {
// do something with your message
}
}
Attention: Never collect a flow from the UI directly from launch or the launchIn extension function to update UI. These functions process events even when the view is not visible. You can use repeatOnLifecycle as the documentation sugests.
You can try BehaviorSubject from rxJava. Is more comfortable to use than poor kotlin.flow. Seems like this link is for you: BehaviorSubject vs PublishSubject
val behaviorSubject = BehaviorSubject.create<MyObject> {
// for example you can emit new item with it.onNext(),
// finish with error like it.onError() or just finish with it.onComplete()
somethingToEmit()
}
behaviorSubject.subscribe {
somethingToHandle()
}
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 would like to use a LiveData for handling kind of notifications, as it is already lifecycle aware, between a custom view and its wrapping fragment. But it seems that a LiveData may loose values : it will only update to its most recent state and also won't fire values during inactive state of its observers.
I've looked at the SingleLiveEvent purpose from Google code samples, but that solution does not seems to be battle tested yet, and the ticket is still open with recent tries to improve the solution.
So I am looking for a simple way to get notified about events, and at the same time not being worried about Lifecycles (that was why I went for LiveData as a first solution), and that could handle multiple observers.
Is there an existing solution for that ? If I try to implement it, it is sure that I will land into at least an anti-pattern.
One easy way (perhaps too easy) is to use callbacks : but the problem is that I need this feature for several callbacks in my component, leading me in a poor architecture. And also, I want a subscribe system, meaning that there could be more than one observer.
One other way, could be to use RxJava and tranform it into a LiveData, with LiveDataReactiveStreams.fromPublisher() : but now the question is whether I will get all values or only the last one. That's the closest solution I could deal with.
As an interesting alternative there could be AutoDispose or RxLifecycle. And an interesting resource I've found : Blog post on LiveData
What are your thoughts, suggestions ?
Also, please notice that I need this communication from a component wrapped into a Fragment (ChessBoard) toward another Fragment (ChessHistory). So they are both lifecycle aware.
It is not ideal, but this does the trick for me:
/**
* This LiveData will deliver values even when they are
* posted very quickly one after another.
*/
class ValueKeeperLiveData<T> : MutableLiveData<T>() {
private val queuedValues: Queue<T> = LinkedList<T>()
#Synchronized
override fun postValue(value: T) {
// We queue the value to ensure it is delivered
// even if several ones are posted right after.
// Then we call the base, which will eventually
// call setValue().
queuedValues.offer(value)
super.postValue(value)
}
#MainThread
#Synchronized
override fun setValue(value: T) {
// We first try to remove the value from the queue just
// in case this line was reached from postValue(),
// otherwise we will have it duplicated in the queue.
queuedValues.remove(value)
// We queue the new value and finally deliver the
// entire queue of values to the observers.
queuedValues.offer(value)
while (!queuedValues.isEmpty())
super.setValue(queuedValues.poll())
}
}
The main problem with this solution is that if the observers are inactive at the time the values are delivered via super.setValue(), then the values will be lost regardless. However, it solves the issue of losing values when several new ones are posted very quickly – which, in my opinion, is usually a bigger problem than losing values because your observer is inactive. After all, you can always do myLiveData.observeForever() from a non-lifecycle-aware object in order to receive all notifications.
Not sure this will be enough for you, but I hope it can help you or give you some ideas about how to implement your own approach.
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