Kotlin Flows in Android

Beratcan Güreş
Getir
Published in
4 min readJun 17, 2023

Flows is a new addition to the Kotlin standard library that provides a reactive programming model for asynchronous and stream-based processing of data. It is designed to handle asynchronous sequences of values that can be transformed and consumed in a non-blocking manner. Flows are particularly useful when dealing with asynchronous operations such as network requests, database queries, or UI events. This article aims to provide a detailed guide on working with Flows in Kotlin.

A suspending function asynchronously returns a single value, but how can we return multiple asynchronously computed values? This is where Kotlin Flows come in.

Basics of Flows

A Flow represents a stream of values that can be asynchronously produced and consumed. It provides a declarative and composable way to handle asynchronous operations. The key concepts of Flows are:

  • Producer: A function or a coroutine that produces values asynchronously. It emits values by using emit() function.
  • Consumer: A function or a coroutine that consumes the emitted values from the flow.
  • Operator: Functions that transform, filter, combine, or perform other operations on the flow.
  • Collector: A function that receives values emitted by the flow and processes them.

Types of Flows

There are two types of flows in Kotlin, cold and hot flows.

Cold Flows (Flow<>) are built using the flow builder and don’t start producing values until one starts to collect them.

Hot Flows(StateFlow and SharedFlow) start producing values immediately. SharedFlow emits all value to all consumers in a broadcast fashion. StateFlow only emits the latest value to its listeners and requires an initial value.

SharedFlow
StateFlow

Collecting Flows

To consume the values emitted by a flow, you need to collect them using a collect operator. The collect operator is a suspending function that receives the emitted values and processes them. For example:

The collect operator is a terminal operator, which means it triggers the execution of the flow.

Flow Builders

There are several ways to build a flow:

- The flow{} builder function allows you to create flows that emit values asynchronously. It can be used with suspending functions or coroutines.

- The flowOf() function allows you to create a flow from a fixed set of values.

- The asFlow() extension function converts various types of collections, sequences, or other objects into flows.

Flow Operators

There are several types of operators that you can use with flows:

- Intermediate Operators like filter, map, onEach, runningReduce, transform, etc.

filter
map
runningReduce
onEach
transform

- Size-limiting Operators like drop(x), dropWhile, take(x), takeWhile, etc.

drop
dropWhile
take
takeWhile

- Terminal Operators like collect, toList, toSet, count, first, last, reduce, fold, etc.

collect
toList
toSet
count
first
last
reduce
fold

- Flattening Operators like flatMapConcat, flatMapMerge, flatMapLatest, etc.

flatMapConcat

Buffering Flows

The .buffer() function allows you to launch the .collect{…} block as a separate coroutine, which can be useful if the producer and the consumer are running in the same coroutine scope.

However, it’s important to be aware of potential buffer overflow if the producer generates a large output stream that the consumer cannot collect quickly.

Error Handling in Flows

Flows provide error-handling capabilities using the catch operator. The catch operator allows you to handle exceptions that occur during flow processing. For example:

Flow Context

Flows are executed in a specific context, which determines the execution context for suspending operations. By default, flows are executed in the context of the caller. However, you can specify a different context using the flowOn operator. For example:

Flow Limitations

Flows have some limitations to consider:

  • Single Collector: Flows support a single collector, and multiple collectors are not supported.
  • Cold Streams: Flows are cold, meaning they start emitting values only when collected.
  • Cancellation: When a flow is canceled, the producer is also canceled, allowing for structured concurrency.

Conclusion

Flows in Kotlin provide a powerful and concise way to handle asynchronous streams of data. They integrate seamlessly with Kotlin Coroutines, making them well-suited for handling asynchronous operations in a non-blocking manner. The bifurcation into cold and hot flows allows for precise control over when values are produced and how they are distributed among consumers. Various flow builders and operators provide flexibility in creating and managing these flows. Overall, Kotlin Flows are a vital tool for any Android developer looking to leverage the power of asynchronous programming in their applications.

For more information, you can check out the official documentation from this link.

Thank you for reading. See you in the next article.

--

--

Beratcan Güreş
Getir
Writer for

Android Engineer @Accenture who is diligent and eager to learn more and more about Android and Kotlin.