DEV Community

Cover image for Optimizing Asynchronous Programming with Flows in Kotlin: Introduction to Flow
Alan Gomes for Comunidade Dev Space

Posted on

1

Optimizing Asynchronous Programming with Flows in Kotlin: Introduction to Flow

1 – Introduction

As the complexity of applications grows, handling multiple asynchronous events in real time becomes essential. To this end, Kotlin offers a powerful tool called Flow, which simplifies the management of asynchronous data flows.

In this article, we will explore:

  • What is a Flow and how does it differ from other approaches like suspend and async .
  • Real use cases of Flow.
  • Tools like Turbine to test data flows.

2 – What is a Flow?

Kotlin Flow is a tool for handling asynchronous data flows. It works like a pipeline that emits values over time, allowing you to handle data streams efficiently.

Main Features:

  1. Asynchronous: Flow emits values in a non-blocking manner.
  2. Cold Stream : A Flow only starts emitting values when there is a "sink" (i.e. when someone consumes the data).
  3. Cancellable: If the collector is canceled, the flow is also automatically canceled.

3 – Creating a Flow

Simple Example of a Flow

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

fun main() = runBlocking {
    val flow = flow {
        for (i in 1..3) {
            emit(i) // Emits a value
            delay(1000) // Simulates a delay between emissions
        }
    }

    flow.collect { value ->
        println("Received: $value")
    }
}
Enter fullscreen mode Exit fullscreen mode

Console Output

Received: 1
Received: 2
Received: 3

Explanation:

  • emit: Sends values to the stream.
  • collect: Consumes the values emitted by the stream.

4 – Comparison: Flow vs. Other Approaches

Appearance Flow suspend async
Continuous data flow Yes No No
Multiple values Yes No (only one value per call) No (returns a single value).
Cancellable Yes Yes Yes
Typical example Event streams Simple asynchronous calls Parallel computations.

5 – Transforming Data with Flow

The real power of Flow lies in its ability to transform the data output with operators.

Example: Using operators like map and filter

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

fun main() = runBlocking {
    val flow = flow {
        for (i in 1..5) {
            emit(i)
        }
    }

    flow
        .filter { it % 2 == 0 } // Filter even values
        .map { it * 10 } // Multiplies the values by 10
        .collect { value ->
            println("Transformed: $value")
        }
}
Enter fullscreen mode Exit fullscreen mode

Console Output

Transformed: 20
Transformed: 40

6 – Testing Flows with Turbine

Turbine is a library that facilitates the validation of values emitted by a Flow.

Example with Turbine

import app.cash.turbine.test
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.*

fun main() = runTest {
// Creates a stream that emits two values with a delay between them
    val flow = flow {
        println("Emitting value 1")
        emit(1) // Emit the first value
        delay(500) // Wait 500ms
        println("Emitting value 2")
        emit(2) // Emit the second value
    }

// Test the flow
    flow.test {
        val firstValue = awaitItem() // Wait for the first value
        println("Value received from stream: $ firstValue ")
        assert(firstValue == 1) // Validates the first value

        val secondValue = awaitItem() // Wait for the second value
        println("Value received from stream: $ secondValue ")
        assert(secondValue == 2) // Validates the second value

        awaitComplete() // Check if the stream has completed
        println("Flow completed successfully!")
    }
}
Enter fullscreen mode Exit fullscreen mode

Expected departure at terminal

Emitting value 1
Value received from stream: 1
Emitting value 2
Value received from stream: 2
Flow completed successfully!

7 – Real Use Cases of Flow

7.1 – Real Time Update

In a chat application, you can use a Flow to stream new messages to the user interface as they arrive from the server.

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

fun main() = runBlocking {
    val messages = flow {
        val newMessages = listOf("Hi", "How are you?", "See you soon!")
        for (message in newMessages) {
            emit(message)
            delay(1000) // Simulates interval between messages
        }
    }

    messages.collect { message ->
        println("New message: $message")
    }
}
Enter fullscreen mode Exit fullscreen mode

7.2 – Batch Data Processing

Imagine a system that needs to process large batches of data at regular intervals. Flow makes this implementation easy.

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

fun main() = runBlocking {
    val batchFlow = flow {
        val lots = listOf("Lot1", "Lot2", "Lot3")
        for (lot in lots) {
            issue(batch)
            delay(2000) // Simulates processing time
        }
    }

    batchFlow.collect { lot ->
        println("Processing: $batch")
    }
}
Enter fullscreen mode Exit fullscreen mode

8 – Conclusion

Flow is a powerful tool for handling asynchronous data in Kotlin , allowing you to emit, transform, and consume values in real time. It is essential for building modern applications, especially in scenarios that require handling continuous streams of events.

Summary:

  1. Flow is ideal for continuous, cancelable data streams.
  2. Operators like map and filter make it easier to transform the outputted data.
  3. Tools like Turbine simplify flow testing.

References:
Official Kotlin documentation on coroutines
Turbine Documentation

Heroku

Deliver your unique apps, your own way.

Heroku tackles the toil — patching and upgrading, 24/7 ops and security, build systems, failovers, and more. Stay focused on building great data-driven applications.

Learn More

Top comments (0)

👋 Kindness is contagious

DEV is better (more customized, reading settings like dark mode etc) when you're signed in!

Okay