๐ŸŽ“ A1Learner
๐Ÿ”
0 of 28 lessons completed

Kotlin Flow Basics

A flow is a cold stream of values built with the flow builder. Its builder code runs only when a terminal call collects the values.

Building a flow runs nothing

The first line prints before collect is called, which proves the builder has not run yet. collect then receives the three emitted values in order.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun numbers(): Flow<Int> = flow {
    for (i in 1..3) emit(i)
}

fun main() = runBlocking {
    val stream = numbers()
    println("built")
    stream.collect { println("value $it") }
}

A flow of words

This builder emits three strings one at a time. collect prints each word as it arrives.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun names(): Flow<String> = flow {
    emit("Ana")
    emit("Bea")
    emit("Cid")
}

fun main() = runBlocking {
    names().collect { println("name $it") }
}

A second terminal call runs the flow again

The first collect fills a list with the emitted values. The count call then runs the same builder a second time and reports three values.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val shared = flow {
        for (i in 1..3) emit(i)
    }
    val seen = mutableListOf<Int>()
    shared.collect { seen.add(it) }
    println("first pass: $seen")
    println("second count: ${shared.count()}")
}

A flow that waits between values

The builder pauses before each emit. collect still receives the values one by one in emit order.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun slowValues(): Flow<Int> = flow {
    for (i in 1..3) {
        delay(50)
        emit(i * 100)
    }
}

fun main() = runBlocking {
    slowValues().collect { println("tick $it") }
}

Example

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val letters = flow {
        emit("a")
        emit("b")
        emit("c")
        emit("d")
    }
    println("before collect")
    letters.collect { println("letter $it") }
    println("letters: ${letters.count()}")
}

Key points

  • The flow builder builds a flow and emit hands one value to the collector.
  • A flow is cold, so building one runs none of its builder code.
  • collect is a terminal call that runs the builder and receives each value.
  • collect is a suspend call, so it runs inside a coroutine or a suspend function.
  • Values reach the collector in the order they were emitted.
  • A second terminal call runs the whole builder again from the start.

In this example, the flow is built first and the before line prints while nothing has run. collect receives the four letters in emit order. The count call runs the builder again and reports four values.

Note

count is a terminal call too, and it runs the flow just like collect does.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Kotlin Playground

Exercise

What happens when a flow is built but never collected?