Kotlin Flow provides asynchronous, reactive streams that can emit multiple values over time. While building flows, two very important topics must be understood:
try/catch, catch(), and onCompletion().This article explains both in detail with examples.
In coroutines, context switching determines whether code runs on Dispatchers.IO,
Dispatchers.Default, or Dispatchers.Main.
With Flow, you can choose:
flowOn()withContext() or collectors on Main
flowOn() changes the context of upstream operations (the producer).
It moves heavy tasks like API calls, file reads, or computation off the main thread.
flowOn() affects only the flow builder (producer), not the collector.
val numbers = flow {
println("Producing on: " + Thread.currentThread().name)
for (i in 1..3) {
delay(300)
emit(i)
}
}.flowOn(Dispatchers.IO)
withContext(Dispatchers.Main) {
numbers.collect {
println("Collected $it on: " + Thread.currentThread().name)
}
}
Producing on: DefaultDispatcher-worker-1
Collected 1 on: main
Collected 2 on: main
Collected 3 on: main
The producer runs on IO, consumer runs on Main safely.
val flowData = flow {
println("Generating on: " + Thread.currentThread().name)
emit((1..1_000_000).sum())
}.flowOn(Dispatchers.Default)
flowData.collect {
println("Result = $it, collected on: " + Thread.currentThread().name)
}
Generating on: DefaultDispatcher-worker-2
Result = 500000500000, collected on: main
Exceptions can occur during:
Flow provides three ways to handle exceptions:
collect block
The simplest way: wrap the collect call in a try/catch.
This catches all exceptions thrown inside the flow.
val myFlow = flow {
emit(1)
emit(2)
emit(3 / 0) // Error
emit(4)
}
try {
myFlow.collect { println(it) }
} catch (e: Exception) {
println("Caught error: ${'$'}e")
}
1
2
Caught error: java.lang.ArithmeticException: / by zero
Note: Flow stops immediately when an exception occurs.
catch() is a flow operator that catches upstream errors and allows fallback values.
flow {
emit("Loading...")
emit(10)
emit(10 / 0) // Exception
emit(20)
}.catch { e ->
emit("Error occurred: ${'$'}e")
}.collect {
println(it)
}
Loading...
10
Error occurred: java.lang.ArithmeticException: / by zero
Here flow doesn't crash — it emits an error message instead of stopping.
onCompletion() runs after the flow finishes.
It tells whether the flow completed normally or with an exception.
flow {
emit(1)
emit(2)
emit(3 / 0) // Crash
}.onCompletion { cause ->
if (cause == null)
println("Flow completed successfully")
else
println("Flow finished with error: ${'$'}cause")
}.catch { e ->
println("Caught: ${'$'}e")
}.collect {
println("Collected: ${'$'}it")
}
Collected: 1
Collected: 2
Flow finished with error: java.lang.ArithmeticException: / by zero
Caught: java.lang.ArithmeticException: / by zero
flowOn() changes producer thread, not consumer.try/catch catches exceptions around collect.catch() catches upstream flow exceptions.onCompletion() runs after flow ends and tells if error occurred.