codingstuff.io
ExploreTutorialsProblemsCS Subjects
Get Started
ExploreTutorialsProblemsCS Subjects
Get Started
codingstuff.io

Master the art of building software through interactive tutorials, real-world problems, and guided projects.

Pune, Maharashtra, India

codingstuffmail@gmail.com

Product

  • Explore
  • Tutorials
  • Problems
  • CS Subjects

Company

  • About
  • Contact
  • Privacy Policy
  • Terms & Conditions
  • Sitemap

© 2026 codingstuff.io. All rights reserved.

Built with ❤️ for developers everywhere

/
/
All Tutorials
🎯

Kotlin

26 / 68 topics
23Coroutines Basics24Suspend Functions25Channels in Coroutines26Flow in Kotlin27Concurrency Model28Exception Handling in Coroutines29Async and Await30Structured Concurrency
Tutorials/Kotlin/Flow in Kotlin
🎯Kotlin

Flow in Kotlin

Updated 2026-04-20
3 min read

Introduction

Flow is a powerful library introduced by JetBrains for handling asynchronous data streams in Kotlin. It provides a way to work with sequences of values that can be emitted over time, similar to RxJava but with a more lightweight and Kotlin-friendly API. This tutorial will guide you through the basics of Flow, its operators, and best practices for using it in your applications.

What is Flow?

Flow is a cold stream of data that emits values asynchronously. It's designed to handle asynchronous operations such as network requests, database queries, or any other operation that might take time to complete. Unlike LiveData or StateFlow, Flow does not hold any value and only emits new values when there are subscribers.

Setting Up

To use Flow in your Kotlin project, you need to add the following dependencies to your build.gradle.kts file:

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
    implementation("org.jetbrains.kotlinx:kotlinx-flow:1.5.2")
}

Creating a Flow

You can create a Flow using the flow builder function. Here's an example of how to create a simple Flow that emits integers from 1 to 3:

import kotlinx.coroutines.flow.*

fun numberFlow(): Flow<Int> = flow {
    for (i in 1..3) {
        delay(1000) // Simulate a delay, e.g., network request
        emit(i)
    }
}

Collecting Values

To consume values from a Flow, you need to collect it. This is typically done using the collect function inside a coroutine:

import kotlinx.coroutines.*

fun main() = runBlocking {
    numberFlow().collect { value ->
        println("Received $value")
    }
}

Operators

Flow provides a rich set of operators that allow you to transform, filter, and combine data streams. Here are some commonly used operators:

Transforming Values

Use the map operator to transform each emitted value:

fun main() = runBlocking {
    numberFlow()
        .map { it * 2 }
        .collect { value ->
            println("Doubled $value")
        }
}

Filtering Values

Use the filter operator to emit only values that meet a certain condition:

fun main() = runBlocking {
    numberFlow()
        .filter { it % 2 == 0 }
        .collect { value ->
            println("Even $value")
        }
}

Combining Flows

Use the combine operator to combine multiple Flows into a single Flow:

fun anotherFlow(): Flow<String> = flow {
    emit("Hello")
    delay(1000)
    emit("World")
}

fun main() = runBlocking {
    val combinedFlow = numberFlow().combine(anotherFlow()) { num, str ->
        "$num $str"
    }
    combinedFlow.collect { value ->
        println(value)
    }
}

Error Handling

Flows can handle errors using the catch operator. This allows you to catch exceptions and emit a fallback value or perform other actions:

fun errorFlow(): Flow<Int> = flow {
    for (i in 1..3) {
        if (i == 2) throw Exception("Something went wrong")
        delay(1000)
        emit(i)
    }
}

fun main() = runBlocking {
    errorFlow()
        .catch { e -> println("Caught an exception: ${e.message}") }
        .collect { value ->
            println("Received $value")
        }
}

Sharing Flows

When you have a Flow that is expensive to produce or needs to be shared among multiple collectors, you can use operators like shareIn or stateIn. These operators ensure that the Flow is only started once and its values are shared with all collectors.

shareIn

The shareIn operator starts the Flow when the first collector subscribes and shares its emissions with subsequent collectors:

fun main() = runBlocking {
    val sharedFlow = numberFlow().shareIn(this, SharingStarted.Eagerly)

    launch {
        sharedFlow.collect { value ->
            println("First collector received $value")
        }
    }

    delay(1000)

    launch {
        sharedFlow.collect { value ->
            println("Second collector received $value")
        }
    }
}

stateIn

The stateIn operator is similar to shareIn, but it also keeps the last emitted value and provides a way to access it synchronously:

fun main() = runBlocking {
    val stateFlow = numberFlow().stateIn(this, SharingStarted.Eagerly, 0)

    println("Initial state: ${stateFlow.value}")

    launch {
        stateFlow.collect { value ->
            println("Collector received $value")
        }
    }

    delay(1000)
}

Best Practices

  1. Use flowOn for Context Management: Use the flowOn operator to specify the context in which the Flow should run, ensuring that your asynchronous operations are performed on the appropriate thread.

  2. Handle Errors Gracefully: Always use the catch operator to handle exceptions and prevent unhandled errors from crashing your application.

  3. Share Flows Wisely: Use shareIn or stateIn when you need to share a Flow among multiple collectors, but be mindful of resource management and potential memory leaks.

  4. Keep Flows Cold: Avoid starting Flows prematurely by keeping them cold until they are actually collected. This ensures that resources are only used when necessary.

  5. Use Coroutines for Concurrency: Leverage Kotlin's coroutines to manage concurrency effectively, allowing you to perform multiple asynchronous operations in parallel without blocking the main thread.

Conclusion

Flow is a powerful and flexible tool for handling asynchronous data streams in Kotlin. By understanding its operators and best practices, you can build robust and efficient applications that respond to changing data over time. Whether you're working with network requests, user input, or any other source of asynchronous data, Flow provides the tools you need to handle it effectively.

Remember to experiment with Flows in your projects and explore additional operators and features as you become more comfortable with them. Happy coding!


PreviousChannels in CoroutinesNext Concurrency Model

Recommended Gear

Channels in CoroutinesConcurrency Model