How can a `SingleLiveEvent` (Android pattern when ...
# coroutines
e
How can a
SingleLiveEvent
(Android pattern when using view models and live data) be modeled using flows? I think that the documentation of
fun <T> ReceiveChannel<T>.receiveAsFlow(): Flow<T>
(https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/receive-as-flow.html) describes this specific use case:
Represents the given receive channel as a hot flow and receives from the channel in fan-out fashion every time this flow is collected. One element will be emitted to one collector only.
So, if I just create a
private val _events = Channel<MyEvent>()
in my
ViewModel
and expose it as
val events = _events.receiveAsFlow()
, would that behave like a single live event? I.e.: one collector only receives the event, all others don't? Or are there additional things to consider? I'm a bit confused about the existence of
(Mutable)SharedFlow
(and the now obsolete
BroadcastChannel
), but I think this will always share emissions between all active collectors (subscribers) and that's precisely what single live event tries to prevent, am I right?
a
Channel.receiveAsFlow
is a good replacement.
SingleLiveEvent
is an antipattern with a lot of workarounds and kludges to try to make LiveData behave closer to something like a Flow or Channel.
☝️ 2
Channel.receiveAsFlow
will allow multiple collectors but each value sent to the channel will only be received by a single collector.
e
SingleLiveEvent
is an antipattern
Agreed. Still, the use case is very clear and (albeit specific) not really readily supported in the Kotlin coroutines API. At this time it seems that using a
SendChannel
as entrance and
ReceiveChannel.asFlow()
as exit is the shortest path between a single event generator and a flow consumers. The good news is that
Channel
is both a
SendChannel
and a
ReceiveChannel
.
Thanks for your reply, Adam!
👍 1
g
I would just use consumable event pattern with StateFlow, it doesn't have this strange behavior in case of multiple observers.
😱 1
c
If it is acceptable to miss events, then I would use a SharedFlow with replayCache = 0. Should behave exactly like RxJava PublishSubject. All active consumers will receive the event once. SharedFlow will not keep it in cache and will not deliver it to late observers - like another android fragment instance created after rotation.
Otherwise StateFlow or Livedata with a consumable event, as Gildor mentioned.
a
The problem there is that you're modeling something flawed. Events can be consumed but state is. State is a statement of fact, and it doesn't matter how many observers observe it
g
Approach with SharedFlow with replayCache = 0 has one big problem, if you don’t want to miss event it will not work, which is common use case on Android, when subscribers cancelled on configuration change
e
A consumable event introduces the need for an additional layer of consumption. This layer is thin, though. Performance wise idk if using a channel received as a flow, or just a state flow with a consumable layer is more taxing on CPU/memory. The conceptual differences (understandability) can also be a reason to choose either approach
I agree with Adam that 'state is'. However, you could explain the consumable event state as a composite state machine: the state is the latest event emitted and the substate is whether or not it is consumed. That is simply a state machine as well
c
channel.asFlow() on the other hand will work only for single observer. Just like SingleLiveData. There is no perfect solution 🙂
g
Performance wise idk if using a channel received as a flow, or just a state flow with a consumable layer is more taxing on CPU/memory
Channel is a lot slower than simple StateFlow, it creates multiple objects on every emit (mostly because it’s thread safe) Anyway, neither consumable wrapper for even, nor even expensive channel emissions probably not a problem at all except cases when you have events every few milliseconds
136 Views