RizTech Academy logo
RizTech Academy
CoroutinesLesson 5 of 530 min

An introduction to Flow

A suspend function returns one value, eventually. But a lot of data arrives as a stream — a sequence of values over time: search results as the user types, location updates, messages in a chat, rows from a database that change. Flow is Kotlin's tool for exactly this — an asynchronous stream of values — and it is the last piece of the coroutines module and a cornerstone of modern Android.

The idea: many values over time

Compare the shapes:

  • A suspend fun gives you one value, later: suspend fun fetchUser(): User.
  • A Flow gives you many values, over time: fun temperatureUpdates(): Flow<Int>.

Think of a Flow as the asynchronous cousin of a Sequence (from the collections module): lazy, and producing values one at a time — but where a sequence is synchronous and pulls values as fast as it can, a Flow is asynchronous and can delay or wait between values, emitting them as they become ready.

Building and collecting a Flow

You build a Flow with the flow { } builder, emitting values with emit, and you consume it by collecting it:

fun countdown(): Flow<Int> = flow {
    for (i in 3 downTo 1) {
        delay(500)          // wait between values — a Flow can be slow and asynchronous
        emit(i)             // push a value to whoever is collecting
    }
    emit(0)
}

fun main() = runBlocking {
    countdown().collect { value ->
        println(value)      // runs for each emitted value, as it arrives
    }
}
3        (after 0.5s)
2        (after 1.0s)
1        (after 1.5s)
0

Read the two halves. The flow { } builder is the producer: it emits values, and it can suspend (delay) between them. collect { } is the consumer: its lambda runs once for each value as it arrives. Between them flows a stream of values over time.

Two crucial properties:

  • Flows are cold. Nothing happens until you collect. The flow { } block does not run when you create the Flow — only when a collector subscribes. Each collection starts the producer afresh. (This is like a Sequence: describing the stream is separate from running it.)
  • collect is a suspend function. Collecting waits for and processes each value, so it must happen in a coroutine — which is why the example is inside runBlocking.

Transforming a Flow — the operators you already know

Here is the lovely part: a Flow supports the same operations as a collection — map, filter, and friends — because they are the same idea applied to a stream:

fun main() = runBlocking {
    countdown()
        .filter { it > 0 }              // drop the zero
        .map { "T-minus $it" }          // transform each value
        .collect { println(it) }
}
T-minus 3
T-minus 2
T-minus 1

filter and map on a Flow do exactly what they do on a list — but lazily and asynchronously, transforming each value as it flows through, without collecting the whole stream first. Everything you learned about map/filter in the collections module transfers directly. This is why the course spent so long on those operations: they are not just for lists, they are the vocabulary of data transformation everywhere in Kotlin, streams included.

Where Flows are used

Flows model any data that arrives over time, which is a huge category on Android:

  • A database that changes — Room (Android's database library) exposes query results as a Flow, so when the underlying data changes, your Flow emits the new results and the UI updates automatically. No manual refresh.
  • User input over time — a search box emitting each query as the user types, so results update live (usually with debounce to wait for a pause in typing).
  • Location, sensors, network status — anything that produces a series of readings.
  • UI state — a StateFlow (a special Flow that always has a current value) is the modern, standard way a ViewModel exposes screen state to the UI, replacing older patterns.

You will use StateFlow constantly in the Android course. The foundation to take now is the shape: a producer emits values over time, and a collector reacts to each — with all the familiar transformation operators in between.

Flow versus suspend function — choosing

The decision is simply about how many values:

  • One value, once: a suspend fun. "Fetch this user", "save this order".
  • Many values, over time: a Flow. "Emit search results as they change", "stream temperature readings", "expose the current UI state".

If you find yourself calling a suspend function in a loop to poll for changes, that is usually a sign the data should be a Flow instead — let it push values to you as they change, rather than you pulling repeatedly.

Check your work

What a Flow represents. An asynchronous stream of many values over time — the multi-value cousin of a suspend fun.

The synchronous analogue of a Flow. A Sequence — lazy, one value at a time — but Flow is asynchronous and can wait between values.

How you build and consume a Flow. Build with flow { } and emit; consume with collect { }.

What "cold" means. Nothing runs until you collect; each collection starts the producer afresh.

Why collect must be in a coroutine. It is a suspend function — it waits for and processes each value.

What operators a Flow supports. The same as collections — map, filter, and friends — applied lazily and asynchronously to the stream.

Four things modelled as Flows on Android. A changing database (Room), live user input, sensors/ location/network status, and UI state (StateFlow).

When to use a Flow versus a suspend function. Many values over time → Flow; one value once → suspend function.

Practice

  1. Write a countdown(): Flow<Int> that emits 3, 2, 1, 0 with a delay between each, and collect it, printing each value as it arrives.
  2. Confirm the Flow is cold: create it but do not collect, and observe that nothing prints.
  3. Add filter { it > 0 } and map { "T-minus $it" } before collecting. Confirm the output transforms just as it would for a list.
  4. Emit values with no delay and collect them — note it behaves like a lazy sequence.
  5. Write a Flow that emits five random numbers with a delay, and collect only those above a threshold with filter.
  6. Explain, for three kinds of data (a fetched user, a search box's queries, a temperature sensor), whether each is a suspend function or a Flow, and why.
  7. Take an imagined "poll every second for changes" loop of suspend calls and describe how a Flow would replace it.

Official documentation

Next module — Testing and Tooling: Gradle, tests, and structuring a real project.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship