Kotlin Flow for Android: Getting Started
In this tutorial, you’ll learn about the basics of Kotlin Flow, and you’ll build an Android app that fetches weather forecast data using Flow. By Dean Djermanović.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Kotlin Flow for Android: Getting Started
30 mins
Hot Versus Cold Streams
A channel, which is a hot stream, will produce values even if aren’t listening to them on the other side. And if you are not listening to the stream, you are losing values.
In the following diagram, getValues()
emits the items via a channel. processValues()
receives 1, 2, 3, and then it stops listening for items. The channel is still producing the items, even when no one is listening:
In practice, you can use a channel to have an open network connection. But that can lead to memory leaks. Or you could forget to subscribe to a channel, and “lose” values.
Hot streams push values even when there is no one consuming them. However, cold streams, start pushing values only when you start collecting!
And Kotlin Flow is an implementation of cold streams, powered by Kotlin Coroutines!
Kotlin Flow Basics
Flow is a stream that produces values asynchronously. Furthermore, Flow uses coroutines internally. And because of this, it enjoys all the perks of structured concurrency.
With structured concurrency, coroutines live for a limited amount of time. This time is connected to the CoroutineScope
you start your coroutines in.
When you cancel the scope, you also release any running coroutines. The same rules apply to Kotlin Flow as well. When you cancel the scope, you also dispose of the Flow. You don’t have to free up memory manually! :]
There are some similarities between Kotlin Flow, LiveData and RxJava. All of them provide a way to implement the observer pattern in your code.
- LiveData is a simple observable data holder. It’s best used to store UI state, such as lists of items. It’s easy to learn and work with. But it doesn’t provide much more than that .
- RxJava is a very powerful tool for reactive streams. It has many features and a plethora of transformation operators. But it has a steep learning curve!
- Flow falls somewhere in between LiveData and RxJava. It’s very powerful but also very easy to use! The Flow API even looks a lot like RxJava!
Both Kotlin Flow and RxJava are implementations of the Reactive Stream specification.
However, Flow uses coroutines internally and doesn’t have some of the features RxJava has. Partly because it doesn’t need some features, and partly because some features are still being developed!
@ExperimentalCoroutinesApi
or @FlowPreview
.
Now that you’ve had enough theory, it’s time to create your first Flow!
Flow Builders
Navigate to main.kt in the starter project.
You’ll start by creating a simple Flow. To create a Flow, you need to use a flow builder. You’ll start by using the most basic builder – flow { ... }
. Add the following code above main()
:
val namesFlow = flow {
val names = listOf("Jody", "Steve", "Lance", "Joe")
for (name in names) {
delay(100)
emit(name)
}
}
Make sure to add the imports from the kotlinx.coroutines
package.
Here, you’re using flow()
to create a Flow from a suspendable lambda block. Inside the block, you declare names
and assigning it to a list of names.
Next, you used a for
loop to go through the list of names and emit each name after a small delay. The Flow uses emit()
send values to consumers.
There are other Flow builders that you can use for an easy Flow declaration. For example, you can use flowOf()
to create a Flow from a fixed set of values:
val namesFlow = flowOf("Jody", "Steve", "Lance", "Joe")
Or you can convert various collections and sequences to a Flow:
val namesFlow = listOf("Jody", "Steve", "Lance", "Joe").asFlow()
Flow Operators
Moreover, you can use operators to transform Flows, as you would do with collections or sequences. There are two types of operators available inside the Flow – intermediate and terminal.
Intermediate Operators
Go back to main.kt and add the following code to main()
:
fun main() = runBlocking {
namesFlow
.map { name -> name.length }
.filter { length -> length < 5 }
println()
}
Here, you used the Flow of names from earlier and you applied two intermediate operators to it:
-
map
transforms each value to another value. Here you transformed name values to their length. -
filter
selects values that meet a condition. Here you chose values that are less than five.
The important thing to notice here is the block of code inside each of these operators. These blocks of code can call suspending functions! So you can also delay within these blocks. Or you can call other suspending functions!
What happens with the Flow is visible on the image below:
Flow will emit values one at a time. You then apply each operator to each of the values, once again, one at a time. And finally, when you start consuming values, you'll receive them in the same order.
Build and run by clicking the play button next to the main function.
You'll notice that nothing happens! This is because intermediate operators are cold. When you invoke an intermediate operation on a Flow, the operation is not executed immediately. Instead, you return the transformed Flow, which is still cold. The operations execute only when you invoke a terminal operator on the final stream.
Terminal Operators
Because Flows are cold, they won't produce values until a terminal operator is called. Terminal operators are suspending functions that start the collection of the flow. When you invoke a terminal operator, you invoke all the intermediate operators along with it:
An example of what would happen with original values, if you were to listen to a Flow:
As you start collecting values, you get one at a time, and you don't block while waiting for new values!
Now go back to the main.kt file and add the collect()
terminal operator:
fun main() = runBlocking {
namesFlow
.map { name -> name.length }
.filter { length -> length < 5 }
.collect { println(it) }
println()
}
Since collect()
is a suspending function, it can only be called from a coroutine or another suspending function. This is why you wrap the code with runBlocking()
.
Build and run the code by clicking the play button. You'll get the following output:
4
3
collect()
is the most basic terminal operator. It collects values from a Flow and executes an action with each item. In this case, you're printing an item to the console. There are other terminal operators available; you'll learn about them later in this tutorial.
You can check out the final code in the Playground-Final project.
Now that you know the basics of Flow, let's move to our weather app, where you'll see Kotlin Flow doing real work! :]