I read multiple articles trying to find an answer explaining difference.
From Roman Elizarov, Channels were added as an inter-coroutine communication primitive.
So they introduced Flow. But Flow is a cold observable, where every subscriber gets their own data (independent from other subscribers). With SharedFlow you get a hot observable that emits independently of having any subscriber(s). You could do the same with ConflatedBroadcastChannel. But JetBrains recommend to use Flow in favor of Channels because of their simpler API.
So if you want to migrate to Coroutines and you need a hot observable multiple subscribers can listen to, you should go with SharedFlow.
When you want to encapsulate your value-producing code so that consumers don’t have to worry about when it starts, stops or fails, use a flow.
when you want to pass values from one coroutine to another, use a channel.
Can you elaborate on those examples? I kinda do not understand when you want to encapsulate your value-producing code so that consumers don’t have to worry about when it starts, stops or fails, use a flow.
Also in the project I work in we are using Channel in MVI View Model implementation.
private val uiEvents = Channel<UiEvent>
(Channel.UNLIMITED)viewModelScope.launch(dispatcher) {
uiEvents.consumeAsFlow().collect { uiEvent ->
fun push(event: UiEvent) {
uiEvents.trySend(event)
}
Is that correct use case for Channel?
What meaning is behind "when you want to pass values from one coroutine to another, use a channel"
Can I understand this that way that In one place I for eg send event of type Unit to display a toast in a fragment then? Suppose only one fragment collects this event. I think this could work with Channel and therefore I am confused.
It all hinges upon the overall architecture of your app and the semantics you require.
Before going into more detail I want to point out one key difference at the start: Channels have a queue semantic, SharedFlows hava a broadcast semantic. That means that an element in a Channel can only be retrieved by one receiver, other receivers will get the next element in the queue and the one after that and so on. A SharedFlow, however, shares its values with anyone who is interested: Every subscriber receives the same values, they are broadcast for everyone to receive.
When strictly adhering to Unidirectional Data Flow (UDF) Flows are a natural tool to use to propagate data from the lower levels up to the UI and you will rarely find a use case for Channels. Events will only be passed down the layers and not passed around.
The uiEvents
from your example, on the other hand, seems to be more like an event queue. And for such a construct a Channel is well suited. A Channel is effectively a blocking queue, meaning you can have the senders and receivers independently from each other using the same queue while the Channel coordinates the shared access.
That is also at the core of "pass values from one coroutine to another": Coroutines run concurrently. They can share the same state, but when you make that state mutable so that they can effectively communicate with each other (one coroutine changing a variable with the other one reading the changed value), you will run into all sorts of synchronizing issues: When was that value changed so it needs to be read again? How to guarantee the value can only be changed by one coroutine at a time? Using a Channel here makes it safe for a sender to be sure their change won't be instantly overwritten by another concurrent sender (it is queued instead), while having a clear signalling mechanism for receivers when there is a new value. You also have some additional configuration options like the buffer size and so on.
The first link in your question refers to an answer that provides another link to a blog entry of Roman Elizarov, the project lead of Kotlin. Here he lays out the historical reasons for Channels: They were a response to coroutines that were introduced to Kotlin, to have a safe way of communication between coroutines. Cold Flows were added later on to allow other, more flexible semantics and eventually the hot SharedFlow arrived. Now, when "New is always better", why use Channels at all? It's mostly about the semantics. A SharedFlow has broadcast semantics, a Channel has queue semantics. When you need a blocking queue you can do that only with a Channel, not a SharedFlow.
The second link in your question talks a lot about the definition of cold and hot. Let me provide another angle here: From the perspective of a collector of a cold Flow the code that produces values is executed in the current coroutine. From that follows:
Multiple collectors each receive their own values, not the same values. That is because the producing code is executed repeatedly for each collector, in their own coroutine. Consider the following code:
val list = List(10) { it } // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
val flow1 = flow {
list.forEach { emit(it) }
}
val flow2 = flow {
repeat(10) { emit(list.random()) }
}
When flow1
is collected multiple times each collector receives the numbers 0 through 9. While they are actually identical to each other, the forEach
loop is nonetheless executed separately for each collector. That becomes more clear when we take a look at flow2
: Now each collector receives a series of 10 randomly chosen values from 0 to 9. Since each collector will run the repeat
loop for themselves, each one is getting different random numbers.
By the same token each Flow collection starts from the beginning. When one collector has collected the first 3 values so far, another collector starting now to collect the same Flow will not get the 4th value, it will start all over and begin with the first value while the initial collector continues entirely unaffected with the 4th value.
No cleanup necessary: The flow only lives as long as the collector is active. When the collector isn't collecting values there is nothing going on, nothing that needs to be stopped or cleaned up.
Hot flows (i.e SharedFlows) on the other hand are actively producing values without needing a collector to run the producing code for them. Again, from a collector's perspective that means:
Flows come with a powerful API allowing various transformations of the content of the Flow and the configuration of the Flow itself even when it is already running. It is also possible to convert a cold Flow to a hot Flow and vice versa. Most of the operations applied to a Flow only affect the upstream, so the collector in the end has no clear picture of what happend in between. That means that a Flow that is presenting itself as a cold Flow to the collector can very well be based on a hot Flow and, for all intents and purposes, can share all its characteristics.
For the collector this doesn't pose too much of a problem, though, it won't make much of a difference anyways in how the flow should be handled. Just keep in mind that if you collect a cold Flow multiple times, you will repeat at least some of the value-producing code.
As a rule of thumb a Flow that is naturally cold should only be made hot at the latest possible moment, when it is actually needed. In Android apps that will usually be the view model where the flow is converted to a StateFlow
, a specially configured SharedFlow that has the semantics of a single observable value (much like a LiveData
or MutableState
).