Skip to Content
Course content

35: Flow for Asynchronous Streams

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Wait, why can't I just use a suspend function for this?

You've already mastered suspend functions, so it's tempting to think they handle everything. But here's the difference: a suspend function returns a single value. It waits, it does its thing, and it gives you one result. But what if you need a stream of values over time? Think of a real-time stock price ticker or a file upload progress bar.

If you tried to do a progress bar with a suspend function, you'd have to pass in a callback or a listener, which quickly turns your code into "callback hell." Flow solves this by allowing you to emit multiple values sequentially. It's essentially a pipeline that pushes data to the consumer whenever it's ready.

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

fun getStockPriceUpdates(): Flow<Int> = flow {
    val prices = listOf(150, 152, 149, 155, 160)
    for (price in prices) {
        delay(1000) // Simulate network lag
        emit(price) // Push the value into the stream
    }
}

I've heard Flow is "cold"—what does that actually mean in practice?

This is where a lot of developers get tripped up. In Kotlin, a Flow is "cold," meaning the code inside the flow { ... } block doesn't actually run until you call a terminal operator—usually collect. It's like a recipe: having the recipe doesn't mean you have a cake; you only get the cake when you actually start baking.

Contrast this with a Channel, which is "hot." A Channel is like a radio station; it broadcasts whether you're listening or not. A Flow, however, starts fresh for every single collector. If you have two different parts of your app collecting the same Flow, they each trigger the producer block independently. I personally find this safer because it prevents memory leaks where a producer keeps running even though no one is listening.

suspend fun main() {
    val priceFlow = getStockPriceUpdates()
    
    println("Flow created, but not started yet...")
    
    // The code inside getStockPriceUpdates() only starts running NOW
    priceFlow.collect { price -> 
        println("Current Price: $price") 
    }
}

How do I clean up or transform the data before it reaches my collector?

You rarely want the raw data exactly as it comes from the source. Maybe you only care about price spikes, or you want to format the number into a currency string. This is where operators come in. Since Flow is built on the same concepts as Java Streams or RxJava, you'll find map and filter very familiar.

Check this out: we can chain these operators to create a highly specific stream of data without cluttering the production logic.

suspend fun main() {
    getStockPriceUpdates()
        .filter { it > 150 } // Only care about prices above 150
        .map { "Price Alert: $$it" } // Transform to a readable string
        .collect { alert -> 
            println(alert) 
        }
}

Does the Flow run on the main thread by default?

Here is a rule of thumb: Flow collects in the context of the person calling collect. If you call collect on the Main thread, and your Flow does heavy database work, you're going to freeze your UI. I've seen this happen in countless code reviews.

To fix this, you don't change the collector; you change the producer using flowOn. This operator changes the CoroutineDispatcher for everything above it in the chain. It's a clean way to say, "Do the heavy lifting on the IO thread, but let the collector handle the results wherever it happens to be."

import kotlinx.coroutines.Dispatchers

fun getDatabaseUpdates(): Flow<String> = flow {
    // Imagine a heavy SQL query here
    emit("User Data Loaded")
}.flowOn(Dispatchers.IO) // This ensures the block above runs on IO thread



📋 Practical Task

Build a Real-Time Sensor Alert System

You are building a system that monitors a temperature sensor. The sensor emits a stream of integers representing degrees Celsius. Your task is to create a Flow that filters the noise and alerts the user only when the temperature is critical.

Requirements:

  • Create a function temperatureSensor() that returns a Flow<Int>. It should emit 5 random values between 10 and 50, with a 500ms delay between each.
  • In your main function, collect this flow but apply the following logic:
    • Filter out any temperature below 30 degrees (ignore them).
    • Map the remaining temperatures to a string: "CRITICAL TEMP: [value]°C".
    • Ensure the flow production happens on Dispatchers.Default using flowOn.
  • Print each resulting alert to the console.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.