1. Introduction

Kotlin Flow provides asynchronous, reactive streams that can emit multiple values over time. While building flows, two very important topics must be understood:

This article explains both in detail with examples.

2. Context Switching in Flows

In coroutines, context switching determines whether code runs on Dispatchers.IO, Dispatchers.Default, or Dispatchers.Main. With Flow, you can choose:

Why flowOn()?

flowOn() changes the context of upstream operations (the producer). It moves heavy tasks like API calls, file reads, or computation off the main thread.

Important Point

flowOn() affects only the flow builder (producer), not the collector.

Example 1 – Switching Producer Context


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)
    }
}
  

Output (example)


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.

Example 2 – Heavy computation with flowOn


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)
}
  

Output


Generating on: DefaultDispatcher-worker-2
Result = 500000500000, collected on: main
  

3. Exception Handling in Flow

Exceptions can occur during:

Flow provides three ways to handle exceptions:

  1. try/catch – around the collect block
  2. catch() operator – inside the flow chain
  3. onCompletion() – runs whether success or failure

4. Using try/catch with Flow

The simplest way: wrap the collect call in a try/catch. This catches all exceptions thrown inside the flow.

Example


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")
}
  

Output


1
2
Caught error: java.lang.ArithmeticException: / by zero
  

Note: Flow stops immediately when an exception occurs.

5. Handling Exceptions Using catch()

catch() is a flow operator that catches upstream errors and allows fallback values.

Example – Using catch() inside Flow


flow {
    emit("Loading...")
    emit(10)
    emit(10 / 0)  // Exception
    emit(20)
}.catch { e ->
    emit("Error occurred: ${'$'}e")
}.collect {
    println(it)
}
  

Output


Loading...
10
Error occurred: java.lang.ArithmeticException: / by zero
  

Here flow doesn't crash — it emits an error message instead of stopping.

6. Using onCompletion() for Final Tasks

onCompletion() runs after the flow finishes. It tells whether the flow completed normally or with an exception.

Example


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")
}
  

Output


Collected: 1
Collected: 2
Flow finished with error: java.lang.ArithmeticException: / by zero
Caught: java.lang.ArithmeticException: / by zero
  

7. Summary