Flow is used to stream data asynchronously. That means flow will send a sequence of data asynchronously and associated collectors will receive the data.
According to the official documentation of Flow,
Flow is an asynchronous data stream that sequentially emits values and completes normally or with an exception.
We can see Flow is divided into two types: Cold Flow and Hot Flow. Many developers, including me, are confused about these two types of flows. Here, I will try to clarify the concept of these two types of flows simply.
Prerequisite:
We need to have a basic idea of Kotlin Coroutine and the basic idea of Flow to understand the difference between Cold Flow and Hot Flow.
Dependecies for Android:
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version")
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version")
Cold Flow:
- Cold flow can send data to only one collector.
- It doesn’t store data. For whom the flow will store data? Where the collector is only one.
- When a new collector is registered, Flow starts its job from the beginning.
Let’s understand this through an example:
val flow = flow<Int> { // flow builder
// A dummy operation
(1..5).forEach {
delay(1000)
emit(it)
}
}
Here, a cold flow is defined as a dummy operation.
val flow = flow<Int> { // flow builder
// A dummy operation
(1..5).forEach {
delay(1000)
emit(it)
}
}
val coroutineScope = CoroutineScope(Dispatchers.Default)
coroutineScope.launch {
flow.collect {
println("First collector: $it")
}
}
A collector is added for the defined flow. When the collector is added, the defined dummy operation in the flow builder is triggered. We can name this collector “First Collector”. After running the program the output is added below.
Output:
First collector: 1
First collector: 2
First collector: 3
First collector: 4
First collector: 5
Now, we will see what happens if one more collector is added after three seconds. We can name this collector the “Second collector.”
val flow = flow<Int> { // flow builder
// A dummy operation
(1..5).forEach {
delay(1000)
emit(it)
}
}
val coroutineScope = CoroutineScope(Dispatchers.Default)
coroutineScope.launch {
flow.collect {
println("First collector: $it")
}
}
delay(3000)
println("3 SECONDS PASSED")
coroutineScope.launch {
flow.collect {
println("Second collector: $it")
}
}
Output:
First collector: 1
First collector: 2
3 SECONDS PASSED
First collector: 3
Second collector: 1
First collector: 4
First collector: 5
Second collector: 2
Second collector: 3
Second collector: 4
Second collector: 5
Here, we can see that after three seconds, when the second collector is added, it prints the value of 1, whereas the first collector prints 3. That means that adding a new collector has invoked the execution of the defined operation from the beginning.
So, We understood that a running operation of a cold flow can’t communicate with any other collector. It works for only one collector without storing any data. When a new collector is added, execution of the defined operation is started from the beginning.
Hot flow:
- Hot flow can send data to multiple collectors.
- It can store data. It can provide stored data to newly added collectors.
- When a new collector is registered, unlike cold flow, it doesn’t start the job from the beginning. Let’s see the example:
val sharedFlow = MutableSharedFlow<Int>()
coroutineScope.launch {
(1..5).forEach {
delay(1000)
sharedFlow.emit(it)
}
}
Here, a SharedFlow is defined. SharedFlow is a flow that has characteristics of hot flow.
val sharedFlow = MutableSharedFlow<Int>()
coroutineScope.launch {
(1..5).forEach {
delay(1000)
sharedFlow.emit(it)
}
}
coroutineScope.launch {
sharedFlow.collect {
println("First collector: $it")
}
}
Here, the first collector is launched for the SharedFlow.
Output:
First collector: 1
First collector: 2
First collector: 3
First collector: 4
First collector: 5
At this stage, when a single collector is added behavior is the same as like as cold flow.
Job Offers
Let’s add the second collector after three seconds of adding the first collector.
val sharedFlow = MutableSharedFlow<Int>()
coroutineScope.launch {
(1..5).forEach {
delay(1000)
sharedFlow.emit(it)
}
}
coroutineScope.launch {
sharedFlow.collect {
println("First collector: $it")
}
}
delay(3000)
println("3 seconds delay passed")
coroutineScope.launch {
sharedFlow.collect {
println("Second collector: $it")
}
}
The second collector is added with a three second delay.
Now let’s see the output:
First collector: 1
First collector: 2
3 seconds delay passed
Second collector: 3
First collector: 3
Second collector: 4
First collector: 4
Second collector: 5
First collector: 5
Here, We can notice after three seconds when the second collector is added, both the first and second collectors have printed 3. Here, Unlike Cold Flow, the Second Collector didn’t receive the value from the beginning of the execution. It received the value from the current position of the execution. Actually, this current value was stored in the flow and passed to the newly added collector.
Conclusion: We understood the main difference between cold flow and hot flow. When a new collector is registered to a cold flow, it launches the execution of the defined operation from the beginning, so the collector also receives the value from the beginning.
For hot flow, when a new collector is registered, It doesn’t launch any new execution of the flow, cold flow sends value from the current position of the flow to the new collector.
Bonus: We can configure the number of stored data by passing the value of the replay parameter.
val sharedFlow = MutableSharedFlow<Int>(replay = 2)
This article is previously published on proandroiddev.com