Characteristics, Elements, and Real-life Use Cases of Flow
Explore the characteristics and key elements of Kotlin Flow, including its terminal and intermediate operations. Learn how Flow works with coroutines to manage asynchronous data streams in practical scenarios like UI events, database changes, and network responses. Understand how to control concurrency and why Flow often replaces channels for reactive programming on Android.
The characteristics of Flow
The terminal operations of Flow (like collect) suspend a coroutine instead of blocking a thread. They also support other coroutine functionalities, such as respecting the coroutine context and handling exceptions. We can cancel flow processing, and structured concurrency is supported out of the box. The flow builder is not suspending and does not require any scope. The terminal operation suspends and builds a relation to its parent coroutine (similar to the coroutineScope function).
Example
The example below shows how the CoroutineName context is passed from collect to the lambda expression in the flow builder. It also indicates that launch cancellation also leads to proper flow processing cancellation.
package kotlinx.coroutines.app
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
// Notice, that this function is not suspending
// and does not need CoroutineScope
fun usersFlow(): Flow<String> = flow {
repeat(3) {
delay(1000)
val ctx = currentCoroutineContext()
val name = ctx[CoroutineName]?.name
emit("User$it in $name")
}
}
suspend fun main() {
val users = usersFlow()
withContext(CoroutineName("Name")) {
val job = launch {
// collect is suspending
users.collect { println(it) }
}
launch {
delay(2100)
println("I got enough")
job.cancel()
}
}
}Flow nomenclature
Every flow consists of a few elements:
- The flow needs to start somewhere. It often starts with a
flowbuilder, conversion from a different object, or helper function. We will explain an essential option in the next chapter, “