Baeldung Pro – Kotlin – NPI EA (cat = Baeldung on Kotlin)
announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

1. Overview

In this tutorial, we’ll go over the differences between takeWhile() and transformWhile(), with a brief introduction to Flows to see where and how we can use these functions.

2. Flows

Flow is an asynchronous data stream that can output multiple values sequentially over time. A simple way to create a Flow of a fixed set of values is by using flowOf() or using asFlow() on a list.

Flows can be used in use cases like streaming database query results or consuming events like user input and outputting it as a stream.

Flows have intermediate operators that are executed lazily and terminal operators that start collecting from the Flow and executing the accumulated operations.

takeWhile() and transformWhile() are intermediate operators that we’ll go through next.

3. takeWhile()

takeWhile() accepts a predicate function that consumes an element from a Flow and returns a boolean indicating whether to continue collecting or not. It collects from a Flow until the predicate function returns false, and discards the remaining elements.

The main difference, when compared to transformWhile(), is that it can only return elements as-is from a Flow, without any transformation.

Another key aspect of takeWhile() is that when the predicate returns false, it stops without emitting that element. 

Here is how we can use takeWhile():

runBlocking {
    val numbersFlow = flowOf(1, 2, 3, 4, 5)

    val taken = testFlow.takeWhile { it < 3 }.toList()

    Assertions.assertEquals(listOf(1, 2), taken)
}

As we can see, when the predicate returns false, it ignores that element and the subsequent elements.

4. transformWhile()

transformWhile() also accepts a predicate function that consumes a Flow element and returns a boolean indicating whether to continue collecting or not.

However, transformWhile() can also transform elements and return results that are different from the original Flow. This is a key feature difference between transformWhile() and takeWhile(). Another difference is that transformWhile() can emit the last element when the predicate returns false, and emit an element multiple times, as it’s more flexible.

Here is an example of how we can transform the elements:

runBlocking {
    val numbersFlow = flowOf(1, 2, 3, 4, 5)

    val transformed = numbersFlow.transformWhile {
        emit("" + it)
        emit("" + it * 2)
        it < 3
    }.toList()

    Assertions.assertEquals(listOf("1", "2", "2", "4", "3", "6"), transformed)
}

As we can see, this code combines multiple features of transformWhile(). It emits values multiple times per element using emit(), includes the last element when the predicate returns false, and returns transformed elements that have different values and types altogether.

Here is a simpler example that is more similar to takeWhile(). It also includes the element when the predicate returns false:

runBlocking {
    val numbersFlow = flowOf(1, 2, 3, 4, 5)

    val transformed = testFlow.transformWhile {
        it
        it < 3
    }.toList()

    Assertions.assertEquals(listOf(1, 2, 3), transformed)
}

This is another reason why transformWhile() is more flexible than takeWhile().

5. Conclusion

In conclusion, we’ve gone over what Flows are, the basic way to create them, and the differences between takeWhile() and transformWhile(). 

We can use takeWhile() when we don’t need the element for which the predicate returns false, and don’t need transformations. We can use transformWhile() when we need something more flexible, like emitting elements multiple times, including the element whose predicate returns false, or transforming the elements altogether.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.