Flow operators help you transform, combine, control, and manage data streams. Operators are divided into:
collect, toList, first, reduce, foldmap, filter, onEach, take, dropRuns BEFORE the flow starts emitting values. Useful for loading states, logs, or counters.
flow {
emit(1)
emit(2)
}.onStart {
println("Flow started")
}.collect { println(it) }
Output:
Flow started
1
2
fun fetchData() = flow {
delay(300)
emit("Data Loaded")
}
fetchData()
.onStart { println("Loading...") }
.collect { println(it) }
Output:
Loading...
Data Loaded
Runs AFTER the flow completes — even if an exception occurs.
flow {
emit(10)
emit(20)
}
.onCompletion { println("Finished") }
.collect { println(it) }
Output:
10
20
Finished
flow {
emit(1)
throw RuntimeException("Boom!")
}
.onCompletion { println("Completed (error or success)") }
.catch { println("Caught: ${'$'}it") }
.collect { println(it) }
Output:
1
Completed (error or success)
Caught: java.lang.RuntimeException: Boom!
Catches upstream exceptions. Does NOT catch exceptions inside collect.
flow {
emit(1)
throw Exception("Error")
}.catch { emit(0) }
.collect { println(it) }
Output:
1
0
flow {
throw IllegalStateException("Invalid")
}
.catch { println("Caught: ${'$'}it") }
.collect()
Output:
Caught: java.lang.IllegalStateException: Invalid
Cancels previous collector block when a new value arrives. Useful for search boxes, typing events.
(1..3).asFlow()
.onEach { delay(200) }
.collectLatest {
println("Collecting ${'$'}it")
delay(300)
}
Output:
Collecting 1 (cancelled by 2)
Collecting 2 (cancelled by 3)
Collecting 3
flowOf("h", "he", "hel", "hell", "hello")
.onEach { delay(100) }
.collectLatest { println("Searching for: ${'$'}it") }
Output:
Searching for: h (cancelled)
Searching for: he (cancelled)
Searching for: hel (cancelled)
Searching for: hell(cancelled)
Searching for: hello
Combines two flows by pairing items by index. Stops when shorter flow ends.
val f1 = flowOf(1, 2, 3)
val f2 = flowOf("A", "B")
f1.zip(f2) { n, c -> "${'$'}n -> ${'$'}c" }
.collect { println(it) }
Output:
1 -> A
2 -> B
(1..3).asFlow().zip((10..30 step 10).asFlow()) { a, b ->
a + b
}.collect { println(it) }
Output:
11
22
33
Emits whenever ANY flow emits, using the latest values from both. Unlike zip, combine does NOT require equal lengths.
val f1 = flowOf(1, 2)
val f2 = flowOf("A", "B", "C")
f1.combine(f2) { a, b -> "${'$'}a + ${'$'}b" }
.collect { println(it) }
Output:
1 + A
1 + B
1 + C
2 + C
val numbers = (1..3).asFlow().onEach { delay(100) }
val letters = flowOf("X", "Y").onEach { delay(200) }
numbers.combine(letters) { a, b -> "${'$'}a & ${'$'}b" }
.collect { println(it) }
Output:
1 & X
2 & X
3 & X
3 & Y
Merges inner flows concurrently, emitting as soon as values are available.
flowOf(
flow { delay(200); emit("A") },
flow { delay(100); emit("B") }
).flattenMerge()
.collect { println(it) }
Output:
B
A
listOf(1, 2, 3).map {
flow {
delay(300 - it * 50)
emit("Flow ${'$'}it")
}
}.asFlow().flattenMerge()
.collect { println(it) }
Output:
Flow 3
Flow 2
Flow 1
Collects flows sequentially. Unlike merge, it waits for one to finish before starting the next.
flowOf(
flow { emit("A1"); emit("A2") },
flow { emit("B1") }
).flattenConcat()
.collect { println(it) }
Output:
A1
A2
B1
listOf(
flow { delay(100); emit(1) },
flow { delay(50); emit(2) }
).asFlow().flattenConcat()
.collect { println(it) }
Output:
1
2
Transforms each emitted value.
(1..3).asFlow()
.map { it * 2 }
.collect { println(it) }
Output:
2
4
6
flowOf("a", "bb", "ccc")
.map { it.length }
.collect { println(it) }
Output:
1
2
3
Filters out unwanted values.
(1..5).asFlow()
.filter { it % 2 == 0 }
.collect { println(it) }
Output:
2
4
flowOf("Dog", "Cat", "Apple")
.filter { it.startsWith("A") }
.collect { println(it) }
Output:
Apple
Stops after emitting N values.
(1..5).asFlow().take(3)
.collect { println(it) }
Output:
1
2
3
flow {
emit("A")
emit("B")
emit("C")
}.take(2)
.collect { println(it) }
Output:
A
B
Skips first N values.
(1..5).asFlow().drop(2)
.collect { println(it) }
Output:
3
4
5
flowOf("A", "B", "C", "D")
.drop(1)
.collect { println(it) }
Output:
B
C
D
Reduces the flow into a single value.
(1..4).asFlow()
.reduce { acc, value -> acc + value }
Output:
10
flowOf("A", "B", "C")
.reduce { acc, value -> acc + value }
Output:
ABC
Same as reduce but starts with an initial value.
(1..3).asFlow()
.fold(10) { acc, value -> acc + value }
Output:
16
flowOf("A", "B")
.fold("Start: ") { acc, value -> acc + value }
Output:
Start: AB
Converts collections, ranges, or sequences into flows.
listOf(1, 2, 3).asFlow()
.collect { println(it) }
Output:
1
2
3
(10..15).asFlow()
.filter { it > 12 }
.collect { println(it) }
Output:
13
14
15
This article covered: