Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
35: Flow for Asynchronous Streams
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 aFlow<Int>. It should emit 5 random values between 10 and 50, with a 500ms delay between each. - In your
mainfunction, 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.DefaultusingflowOn.
- Print each resulting alert to the console.
There are no comments for now.