Kotlin Flow Errors
A flow can fail partway through its values. The catch operator handles that failure, and the onCompletion operator reports how the flow ended.
Values first, then the catch
The flow emits two values and then throws. catch prints the message from the failure, and the line after collect still runs.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val numbers = flow {
emit(1)
emit(2)
throw IllegalStateException("stream failed")
}
numbers
.catch { e -> println("Caught: " + e.message) }
.collect { println(it) }
println("After collect")
}A normal end has a null cause
The flow emits two values and ends on its own. onCompletion reads its cause and prints that the end was normal.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val numbers = flow {
emit(10)
emit(20)
}
numbers
.onCompletion { cause ->
if (cause == null) println("Finished normally") else println("Finished with error")
}
.collect { println(it) }
}onCompletion sees the failure too
onCompletion sits above catch in this chain, so it reads the failure first. catch then handles the same failure and collect returns.
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val numbers = flow {
emit(5)
throw IllegalStateException("burst")
}
numbers
.onCompletion { cause ->
if (cause == null) println("Normal end") else println("Ended with: " + cause.message)
}
.catch { e -> println("Handled: " + e.message) }
.collect { println(it) }
println("After collect")
}Example
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
val readings = flow {
emit(3)
emit(6)
emit(9)
throw IllegalStateException("sensor offline")
}
readings
.onCompletion { cause ->
if (cause == null) println("Stream closed cleanly") else println("Stream closed by a failure")
}
.catch { e -> println("Recovered from: " + e.message) }
.collect { println("Reading: " + it) }
println("Done")
}Key points
- A flow that throws stops emitting at the failure point.
- The catch operator receives the failure as its lambda parameter.
- Code after the collect call still runs once catch has handled the failure.
- The onCompletion operator runs when the flow ends, in success or in failure.
- onCompletion receives a cause value that is null after a normal end.
- A non-null cause carries the failure that ended the flow.
In this example, the flow emits three readings and then throws. collect prints each reading as it arrives. onCompletion reports that a failure closed the stream, catch prints the failure message, and the final line still runs.
Note
catch only sees failures from the flow above it in the chain.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท Kotlin Playground
Exercise
What does onCompletion receive after a flow ends normally?