In Kotlin’s coroutines, a flow is essentially a sequence of values that can emit values over time. However, not all flows behave in the same way. Some flows can be classified as either cold or hot.
A cold flow produces its values only when a collector is listening to it. In other words, it doesn’t do any work until someone starts consuming the flow. On the other hand, a hot flow starts emitting values regardless of whether there are any collectors present or not. A hot flow is almost like a stream, and emits values continuously.
One way to create a cold flow in Kotlin is to use the ‘flow‘ builder function, as shown in the following example:
fun simpleFlow(): Flow<Int> = flow {
for (i in 1..3) {
delay(1000)
emit(i)
}
}
In this example, the function ‘simpleFlow‘ returns a cold flow that emits the values 1, 2 and 3 with a delay of 1 second between each emission. This flow doesn’t start emitting values until someone starts listening to it using a ‘collect‘ operator. For example:
fun main() = runBlocking<Unit> {
val flow = simpleFlow()
println("Calling collect...")
flow.collect {
println(it)
}
println("Done collecting!")
}
When we run this program, we get the following output:
Calling collect...
1
2
3
Done collecting!
The output shows that the flow’s values are emitted only when the ‘collect‘ operator is called.
On the other hand, to create a hot flow in Kotlin, we can use the ‘broadcast‘ builder function along with a ‘Channel‘. A ‘Channel‘ is essentially a queue that can send and receive values. The following example shows how to create a hot flow:
fun hotFlow(): Flow<Int> = broadcast {
for (i in 1..3) {
send(i) // Send the value to the channel
delay(1000) // Wait for 1 second
}
}.asFlow()
In this example, the ‘broadcast‘ function returns a ‘BroadcastChannel‘ that is used to send the values to the flow. The ‘asFlow()‘ operator converts the ‘BroadcastChannel‘ into a flow that can be consumed by a collector.
To consume a hot flow, we can use the ‘collect‘ operator or the ‘launchIn‘ operator. The ‘launchIn‘ operator launches a coroutine that runs continuously and collects the values emitted by the flow.
fun main() = runBlocking<Unit> {
val flow = hotFlow()
println("Calling collect...")
flow.collect {
println(it)
}
// Alternatively, we can use launchIn to collect values in the background
/*flow.launchIn(CoroutineScope(Dispatchers.Default)) {
println(it)
}*/
println("Done collecting!")
}
When we run this program, we get the following output:
Calling collect...
1
2
3
Done collecting!
In this output, we can see that all the values are emitted immediately, with a delay of 1 second between each value.
Now, when should we use each type of flow? Cold flows are useful when we want to emit a finite number of values, and the values are expensive to calculate. By emitting values only when needed, we can avoid doing unnecessary work. Hot flows, on the other hand, are useful when we want to emit or receive values continuously. For example, we could use a hot flow to monitor a database or a network stream for updates.
In general, it’s a good idea to use cold flows whenever possible, and only use hot flows when there is a specific reason to do so. This is because hot flows can be more difficult to reason about and can lead to unexpected behavior if not used correctly.