map()
, flatMap()
, and flatten()
While both the Android and iOS operating systems are placing more and more weight on the contemporary functional programming patterns and paradigm introduced by the modern programming languages they employ — i.e. Kotlin and Swift, respectively — the need to use and manipulate collections is growing exponentially with it.
The Kotlin Standard Library has included a series of extension functions meant to add transformations into a collection, and these functions are also meant to address the growing need for good, quick, and efficient collection manipulation.
There are four main types of collection transformations as provided by the Kotlin Standard Library: Map, Zip, Associate, and Flatten. For this article, I will concentrate on only two of these; i.e., Map and Flatten, because this set of extension functions has a special association and is forever zipped together because of the similar use cases they aim to organize.
In this article, we’ll cover:
Let’s start with the basics of Map. The transformation function of map()
is defined by Kotlin’s official documentation:
The mapping transformation creates a collection from the results of a function on the elements of another collection. It applies the given lambda function to each subsequent element and returns the list of the lambda results. The order of results is the same as the original order of elements.
To better explain what this means, let’s start off with a simple example:
In this example, we have a Set
of three values: the numbers 1, 2, and 3, which are then processed using a map()
function with a predicate that multiplies each one of the elements from the numbers
collection by 5
, and returns the resulting list of:
[5, 10, 15]
The map()
function has a counterpart function that may leverage the element’s index more conveniently. Namely, mapIndexed()
, is perfect to use when the index of an element may be needed in the desired transformation.
Here’s a simple example using the same basic Set
collection from before, followed by its result:
[1, 1, 1]
Following Kotlin’s null-safety initiative, both of these extension functions also come with versions of them that allow for the resulting collection to ignore all values that may be nullified by the given transformation.
Both mapNotNull()
and mapIndexedNotNull()
were conveniently created, keeping in mind that null values may still result in some cases, and their usage is showcased in the next code block:
[6, 3, 9] [6, 3, 9]
Lastly, using a Map transformation with a Map
collection opens up two parallel options. Transformations that want to be applied to the keys of the map should use the mapKeys()
function, while transformations applied to the values of the map should instead use the mapValues()
function.
As the official documentation explains, both of these functions use the transformations that take a map entry as an argument, so you can operate both its key and value:
{FIRST=1, SECOND=2, THIRD=3, FOURTH=4} {first=6, second=8, third=8, fourth=10}
As the Kotlin documentation on collection transformations explains, operating with nested collections sometimes requires the use of the standard library functions that provide flat access to nested collection elements. There are two main functions that provide these kinds of solutions: flatten()
and flatMap()
, both of which are considered part of the Flatten collection transformation functions group.
First there’s flatten()
, a function that will take in a collection of collections and return a singular List
containing all of the elements that the nested collections used to have.
In this first example for flatten()
, we take a nested list of Set
s, and we’ll flatten them into a single List
that will simply show all the values:
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
The flatten()
function is capable of flattening nested collections containing any kind of object types. To further demonstrate that, here’s a very similar example that uses String
instead:
[Los Angeles, San Francisco, Sacramento, Ausitn, San Antonio, Houston, Dallas, Mexico City, Monterrey, Guadalajara]
Additionally, this function is not limited by the object type inside of its nested collections. This means that the inner collections can contain different object types inside of it, and the flatten()
function should still be able to construct a resulting List
of type Any
that will contain all of the results of varied types:
[1, 2, 3, one, two, three, 1.0, 2.0, 3.0]
The other Flatten collection transformation function that Kotlin provides is flatMap()
. As per its documentation, this second function works very similarly to flatten()
but gives the additional flexibility of providing a way to process the nested collections by taking a function that maps a collection element to another collection. Because of this, flatMap()
returns a single list of its return values on all the elements.
flatMap()
behaves as a subsequent call ofmap()
— with a collection as a mapping result — andflatten()
.
— kotlinlang.org
Let’s start with an example we used to testflatten()
, but add some variation to it in order to emphasize the difference between both these options:
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
In the example above, we’ve recycled one of the flatten()
examples, but instead we’re using the flatMap()
function to arrive at the same result.
As it’s visible here, the flatMap()
function not only flattens the nested collection, but gets an additional oportunity to add a transformation to the inside collection type — in this case the different Set
s — through the map()
function that gets triggered on the inner collection for further manipulation:
[6, 7, 8, 9, 4, 5, 1, 2, 3, 0]
In this second flatMap()
example, we’re yet again taking the same code, but this time we are adding an extra manipulation as part of the map()
function step, in order to further expose the main differences between both of the Flatten functions.
The flatMap()
function is especially useful when dealing with complex data classes or POJOs, as it will give the opportunity to find or further transform the information that is nested underneath the first layer.
In the example below, we’ll use a mildly complex data class
to explore more complicated data manipulations inside of a flatMap()
call:
[Austin, San Antonio, Dallas, Houston, Los Angeles, San Francisco, Sacramento, Monterrey, Guadalajara, Mexico City]
And just as its flatten()
compatriot does, the flatMap()
function is also capable of flattening collections of different types, which end up being interpreted as collections of type Any
:
[one, two, three, 1, 2, 3]
Lastly, the flatMap()
function is also capable of adding an additional map()
transformation inside of its transformation block in order to add even more transformations to the collection on the innermost layers of the nested collection groups:
[Austin:Texas, San Antonio:Texas, Dallas:Texas, Houston:Texas, Los Angeles:California, San Francisco:California, Sacramento:California, Monterrey:Mexico, Guadalajara:Mexico, Mexico City:Mexico]
This last example shows the pinnacle usage of the flatMap()
function. The combination of a flatMap()
with a subsequent map()
call will allow for the most versatile and granular manipulation of lists, while providing the user with a flat interpretation of such for any kind of needs.
All three Kotlin data mapping functions — map()
, flatten()
, and flatMap()
— are part of a group of transformation functions that specifically work with Kotlin collections. They aid with the interpretation and manipulation of complex and/or nested collections, that might be too hard to analyze in their default state.
By leveraging any of these powerful functions correctly, developers should be able to better extract the information needed from any kind of complex collection structure. This technique is imperative in maximizing the efficiency and reliability that goes into the developer’s work. Correctly using these may come in extra handy when dealing with the all-so popular Reactive Streams coding paradigm that popular concurrency libraries like RxJava and Kotlin Coroutines enable you to do.
LogRocket is an Android monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your Android apps.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your Android apps — try LogRocket for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.