You’re correct that the channel “hides” the fact that there can be only one collector. I would say that’s actually desirable behaviour but perhaps I should call it out more in the article.
Adding sharedIn
within the view model exposes some issues as well - all related to multiple collectors.
Consider what happens if events are emitted in the view model before there are any collectors collecting. If there are, for example, two collectors in the fragment/activity, when the view is initialized (say on a configuration change) the first collector will consume all the buffered events before the second collector even calls the collect
function to start collecting.
Once both collectors are initialized, then yes, shareIn
does its job and both collectors observe events as they are emitted.
In order to do what you’re proposing we’d need something like RxJava’s connectable observer where the stream does not start emitting until a common “connect” method is called on the stream. That is, it gives time for all possible collectors to be created and start collecting. Only then is it safe to start the stream of events.
(I suspect this won’t format well in a comment but here goes…)
Not to drag out this already long comment… I have a small test app that demonstrates the problem. (I’ll omit the code or this comment will get even longer. The output is what is important.) This test app calls the view model on every lifecycle event. The view model in turn emits the name of the lifecycle event as a value on the channel. The channel, is then consumed by a collector again.
Fragment event: ON_PAUSE
ViewModel: Emitting ON_PAUSE 5 on the channel
Fragment: observe in collector received ON_PAUSE 5 in lifecycle state STARTED
Fragment: observe in collector2 received ON_PAUSE 5 in lifecycle state STARTED
Fragment event: ON_STOP
ViewModel: Emitting ON_STOP 6 on the channel
Fragment: observe in collector received ON_STOP 6 in lifecycle state CREATED
Fragment: observe in collector2 received ON_STOP 6 in lifecycle state CREATED
Fragment event: ON_DESTROY
ViewModel: Emitting ON_DESTROY 7 on the channel
Fragment event: ON_CREATE
ViewModel: Emitting ON_CREATE 8 on the channel
Fragment: observe in collector received ON_DESTROY 7 in lifecycle state STARTED
Fragment: observe in collector received ON_CREATE 8 in lifecycle state STARTED
Fragment event: ON_START
ViewModel: Emitting ON_START 9 on the channel
Fragment: observe in collector received ON_START 9 in lifecycle state STARTED
Fragment: observe in collector2 received ON_START 9 in lifecycle state STARTED
Note that the second collector never receives the destroy or create events as they were fully consumed by the first collector before the second could even register to see the events.
Your point is very well taken though. The issue of multiple collectors needs to be highlighted a bit more in the article.