All you need to know about Kotlin FLOW (Part 2)

In this series, I will explain Kotlin’s flow in the easiest way to understand

Ahmad Kazimi
4 min readApr 21, 2022

Today will take a closer look at Flow operators and how to use them.

First, what are the Flow Operators ?!

The operator is a way or something to decide what will happen with a single emission of the flow.

Filter

for example, let’s say you have countDownFlow like the previous article
and you need to receive only the even values then you need to use one of the operators and for this purpose, we can use the filter operator

class MainViewModel : ViewModel() {

private val startValue = 10
private var currentValue = startValue

private val countDownFLow = flow<Int> {
emit(currentValue)

while (currentValue > 0) {
delay(1000L)
currentValue--

emit(currentValue)
}
}

init {
collectData()
}

private fun collectData() {
viewModelScope.launch {
countDownFLow
.filter { time ->
time % 2 == 0
}
.collect { time ->
print("The current time is $time")
}
}
}
}

Output :

The current time is 10
The current time is 8
The current time is 6
The current time is 4
The current time is 2
The current time is 0

Map

We have another operator called map you might know this operator from List. you can use that to map the emission to a new value.

private fun collectData() {
viewModelScope.launch {
countDownFLow
.filter { time ->
time % 2 == 0
}
.map { time ->
time + 2
}
.collect { time ->
println("The current time is $time")
}
}
}

Output :

The current time is 12
The current time is 10
The current time is 8
The current time is 6
The current time is 4
The current time is 2

onEach

Very obvious from the name of this operator it returns the emission and you can do anything you want with that emission it’s pretty much a collect
but the main difference here between collect and onEach that is

collect
: makes sure that the flow has finished and doesn’t return anything
onEach: don’t worry about the Flow and return the Flow itself.

We can use it to launch the flow in a little different way

private fun collectData() {
countDownFLow.onEach { time ->
println("The current time is $time")
}
.launchIn(viewModelScope)
}

Terminal operators

they called Terminal operators because they terminate the Flow

Count

This will count values that match specific conditions.

private fun collectData() {
viewModelScope.launch {
val count = countDownFLow
.filter { time ->
time % 2 == 0
}
.map { time ->
time + 2
}
.count { time ->
time % 2 == 0
}
println("The emission count is $count")
}
}

Output :

The emission count is 6

Reduce

Accumulates value starting with the first element and applying the operation to current accumulator value and each element.

private fun collectData() {
viewModelScope.launch {
val reduceResult = countDownFLow
.filter { time ->
time % 2 == 0
}
.reduce { accumulator, value ->
accumulator + value
}

println("The reduce result is $reduceResult")
}
}

Output :

The reduce result is 30

Fold

It is pretty much as Reduce but with init value

private fun collectData3() {
viewModelScope.launch {
val reduceResult = countDownFLow
.filter { time ->
time % 2 == 0
}
.fold(100) { accumulator, value ->
accumulator + value
}

println("The reduce result is $reduceResult")
}
}

Output :

The reduce result is 130

Flattening operators

What does Flattening mean?
let's say you need to return a single list of all elements from all collections in the given collection.

for example, we have [[1,2,3] [4,5]] and you need a single list from all that [1,2,3,4,5]
here is where flattening operators do an amazing job
in this demonstration we used List but Kotlin flow has something very similar

don’t forget we are flattening flows here not List

Let’s take a look

class MainViewModel : ViewModel() {

private val firstNameFlow = flow {
emit("Ahmad")
}

init {
collectData()
}


private fun collectData() {
viewModelScope.launch {

firstNameFlow
.flatMapConcat { firstName ->
flow {
emit(firstName)
emit("Kazimi")
}
}
.collect {
println("My name is : $it ")
}
}
}
}

let’s break it down
1. declared firstNameFlow responsible for first name emit.
2. used flatMapConcat operator which is return Flow {}
3. inside flatMapConcat{} we declared our second Flow that emit lastNameFlow
4. Since we have 2 flows but we using flatMapConcat{} we need only one collector for both flows to collect{} the values and

There are another 2 flatten operator

flatMapLatest {}
it’s sound familiar from collect latest it’s simply dropping the emission of the inner flow in case it finishes before the first flow which is firstNameFlow finish, it’s Ignore the emission in the middle

flatMapMerge {}
we discourage its usage in a regular application-specific flows. Most likely, suspending operation in map operator will be sufficient and linear transformations are much easier to reason about.

see you in part 3

Happy coding :)
Kazimi

--

--

Ahmad Kazimi

Turning pizza and coffee into magic, also called Android apps.