We're thinking about adding guidance in Android ab...
# flow
p
We're thinking about adding guidance in Android about how to expose events/effects from a viewmodel and we need a flow that : • has a way to limit its capacity (usually 2-5) • replays everything in its capacity when going to 0 to 1 collectors but does not replay anything from 1 to 2 and beyond (as they're already consumed) We looked at
SharedFlow
(but it replays everything to a second observer),
Channel(CONFLATED).*receiveAsFlow*
but only one observer gets updates (and we discarded
StateFlow
,
consumeAsFlow()
, and
.broadcast()
/cc @elizarov @Manuel Vivo
e
Can you elaborate a bit more on what are you trying to achieve? What's your use case?
p
We're exposing messages to a UI that are shown once, in a Snackbar. They can be queued before there's a collector listening. The first collector gets all the queued up items, but the second should only get the new ones. In Android there are certain situations in which there might be two views collecting at the same time. An edge case, but can happen. We prefer to broadcast in that case.
e
I’m not sure I’m fully grasping the need to have two collecting views to both get notifications. Will it mean you’ll have two snackbars shown or?
m
There’s no need to have two views collecting them. However, it could be the case that during a transition, the view that is going away consumes the message instead of the one that remains on the screen 🤔 and that’d practically mean that the message got lost
e
Could the transition logic be fixed so that this problem does not happens (as opposed to implementing a special broadcasting logic that makes distinction between first and other subscribers)?
m
Could the transition logic be fixed
Wish we could 🙂 With Compose, things get easier as you can easily host state to avoid this. However, with existing Views… things get more complicated 😞 and it’s difficult to come up with a recommendation that fits most use cases
p
Yes. And we do in Compose. However we'd like to have a solid solution for other situations, if possible.
e
The following solution might work with some caveats: • Use a regular channel on the sender side. You can use
Channel(n)
to set the number of notifications that will be always kept even w/o subscribers. • For views, you do
channel.asFlow().shareIn(scope, start = SharingStarted.whileSusbcribed)
. It means that it will start reading from the channel only when there is at least one subscriber, so the first subscriber starts sharing and drains the channel, the second subscriber will not replay anything (we don’t configure replay in sharing operator)
The caveat is that if the first subscriber appears, drains the channel, and then immediately gets killed, then the messages will get lost. But I believe that any solution of this kind will suffer from this problem by the very definition of how requirements are set.
p
yes that's a caveat that we would have to include in the guidance. I'm trying it out now but I think it's what we want. Thank you!
m
I think that should be safe to use. It’s unlikely that the first subscriber is killed without the second one being subscribed already
p
well... set a snackbar and a navigation event at the same time 💥
We ended up doing :
Copy code
val _channel = Channel<Int>(capacity = 2, onBufferOverflow = BufferOverflow.DROP_OLDEST)
val exposedFlow: SharedFlow<Int> = _channel
    .consumeAsFlow().shareIn(scope, SharingStarted.WhileSubscribed())
(note the
consumeAsFlow
) and this works perfectly thanks
e
Good.
I'll cc @Vsevolod Tolstopyatov [JB] here just to make sure he's aware of this thread, too.