What is Flow

Many Values Over Time

A suspend function returns one value. A Flow<T> emits many values one after another, asynchronously: live cricket scores, search results while typing, rows from a database that keep changing.

ReturnsExample
normal functionone value, nowfun total(): Int
suspend functionone value, latersuspend fun fetchUser(): User
Sequence<T>many values, now (blocking)generateSequence
Flow<T>many values, later (suspending)fun prices(): Flow<Int>

Flows Are Cold

Example

A flow is only a recipe. The code inside flow { } runs each time someone calls collect, and not before.

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

fun scores(): Flow<Int> = flow {
    println("  (flow started)")
    for (run in listOf(4, 6, 1)) {
        delay(100)
        emit(run)
    }
}

fun main() = runBlocking {
    val flow = scores()
    println("Flow created, nothing ran yet")
    println("Collector 1:")
    flow.collect { println("  got $it") }
    println("Collector 2:")
    flow.collect { println("  got $it") }
}

Output

Flow created, nothing ran yet
Collector 1:
  (flow started)
  got 4
  got 6
  got 1
Collector 2:
  (flow started)
  got 4
  got 6
  got 1

Flow Builders

Example

Different ways to create a flow.

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

fun main() = runBlocking {
    println(flowOf("UPI", "Card", "Wallet").toList())
    println(listOf(10, 20, 30).asFlow().toList())
    println((1..5).asFlow().toList())
    println(emptyFlow<String>().toList())

    val countdown = flow {
        for (i in 3 downTo 1) emit("T-$i")
        emit("Liftoff")
    }
    println(countdown.toList())
}

Output

[UPI, Card, Wallet]
[10, 20, 30]
[1, 2, 3, 4, 5]
[]
[T-3, T-2, T-1, Liftoff]

For callback-based APIs (location updates, Firebase listeners, sensors) use callbackFlow:

fun FusedLocationProviderClient.locationFlow(request: LocationRequest): Flow<Location> = callbackFlow {
    val callback = object : LocationCallback() {
        override fun onLocationResult(result: LocationResult) {
            result.lastLocation?.let { trySend(it) }
        }
    }
    requestLocationUpdates(request, callback, Looper.getMainLooper())
    awaitClose { removeLocationUpdates(callback) }   // runs when the collector stops
}

Flow Rules

  • emit must be called from the same coroutine as flow { }. Do not launch inside flow { } and emit from there; use channelFlow for that.
  • Do not use withContext inside flow { } to change the thread of emissions; use flowOn.
  • Collection is sequential: the next value is produced only after the collector finished the previous one (unless you add a buffer).