Adam McNeilly

Hidden Gems In Kotlin StdLib

We've been using Kotlin at OkCupid since about mid 2017, and our entire Android team loves it. If you've been developing in, or even researching Kotlin, you're aware of how much more enjoyable it is compared to Java.

We know a lot of the benefits - no semicolons, nullable types, extension methods, etc. However, I think there's a number of little hidden gems in the language that, sure, you can find in the documentation, but are not always called out in introductory Kotlin posts. So we're going to walk through a couple for you!

Rather than make this a grab bag of Kotlin tips and tricks, we can structure hidden gems into two categories:

  1. Included methods
  2. Language features

Included Methods

This section is to discuss methods built into classes in the standard library that didn't exist in Java, and you may not be aware of in Kotlin.

Strings

One class that I believe is worth going over is the String class. This is a class that we all have to work with in our careers, so it's important to know the power of Kotlin's String class.

First, let's take a trip down memory lane and remember our days as a Java developer. In Java, if we wanted to work with Strings to do things we couldn't do with the built in Java String class, we'd import the Apache Commons library for their StringUtils class. It would give us some helpful methods:

    StringUtils.IsBlank("  "); // True
    StringUtils.SubstringBefore("Adam.McNeilly", "."); // "Adam
    StringUtils.SubstringAfter("Adam.McNeilly", "."); // "McNeilly"
    StringUtils.LeftPad("1", 2); // " 1"
    StringUtils.Chop("abc"); // "ab"

However, thanks to the wonderful developers at JetBrains, all of those above methods are built in:

    val blank = "   ".isBlank() // Also: CharSequence?.isNullOrBlank
    val first = "Adam.McNeilly".substringBefore('.') // "Adam"
    val last = "Adam.McNeilly".substringAfter('.') // "McNeilly"
    val withSpaces = "1".padStart(2) // " 1"
    val endSpaces = "1".padEnd(3, '0') // "100"
    val dropStart = "Adam".drop(2) // "am"
    val dropEnd = "Adam".dropLast(2) // "Ad"

You'll notice here that we get rid of the Java-esque way of using static methods with the format HelperClass.doSomething(input), and use the more Kotlin idiomatic approach of calling a method on the object itself in the format input.doSomething(). They're effectively the same thing, Kotlin extension functions are essentially just syntactic sugar for static methods.

There's even more that the Kotlin strings class provides, just to highlight a few:

    "A\nB\nC".lines() // [A, B, C]

    "One.Two.Three".substringAfterLast('.') // "Three"
    "One.Two.Three".substringBeforeLast('.') // "One.Two"

    "ABCD".zipWithNext() // [(A, B), (B, C), (C, D)]

    val nullableString: String? = null
    nullableString.orEmpty() // Returns ""

The last two methods, zipWithNext() and orEmpty() actually apply to collections as well! orEmpty() is one of my personal favorite methods in Kotlin - being able to take a nullable string or nullable collection and convert it to an empty value (that is not null) is a huge time saver compared to an alternative such as the elvis operator: val nonNullString = nullString ?: "". Note: You can also use this on nullable collections to return an empty list!

Symmetry

One of the great things about working in Kotlin across the board is the symmetry between methods. Sure, I might often want the substring before a character, but what about after? Maybe I want to pad the start of a string, but I could also want to pad the end. The language developers took this into consideration, and made sure we had as much symmetry as possible:

    substringBefore()
    substringAfter()

    isEmpty()
    isNotEmpty()

    padStart()
    padEnd()

    drop()
    dropLast()

    trimStart()
    trimEnd()

Note: As reddit user Kodablah explains, there's not always perfect symmetry. You may find a few exceptions to this.

Collections

On that note, there's a lot you can do with collections in Kotlin that we couldn't do before, as Java developers.

Similar to Apache Commons, but without an external dependency, we had a Collections class in Java which had a bunch of static methods to do the things we want:

    Collections.sort(myList);
    Collections.max(myList);
    Collections.min(myList);
    Collections.shuffle(myList);
    Collections.reverse(myList);
    Collections.swap(myList, 1, 2);

As with Strings, we don't want to worry about this Java static method type approach, so you can do all of the above in the nice Kotlin idiomatic way:

    myList.sort()
    myList.max()
    myList.min()
    myList.shuffle()
    myList.reverse()
    myList.swap(1, 2)

Iterating Collections

Kotlin provides a lot of really powerful ways to iterate over a collection. I don't just mean for loops, I mean iterating with a purpose. A common example would be doing a sequential search for an item that meets some condition. In Java, we're used to having to write this:

    public Person getAdam(List<Person> people) {
        for (Person person : people) {
            if (person.getName().equals("Adam")) {
                return person;
            }
        }

        return null;
    }

This was the best we could do in Java, and isn't awful, but thankfully in Kotlin we actually have a one line method to do this that just accepts the condition we want to check:

    fun getAdam(people: List<Person>): Person? {
        return people.firstOrNull { it.name == "Adam" }
    }

The firstOrNull method will return the first item in a collection that meets the condition supplied. If you look at the source code, you'll see that it does the exact same thing as our Java for loop. So we keep the exact same performance, but gain all of this great readibility.

The options here are pretty extensive, and also support the symmetry we see around the stdlib:

    myList.filter { }
    myList.filterNot { }
    myList.filterIsInstance()
    myList.filterNotNull { }

    myList.first { } // Also: indexOfFirst { }
    myList.firstOrNull { }
    myList.last { } // Also: indexOfLast { }
    myList.lastOrNull { }
    myList.single { }
    myList.singleOrNull { }

    myList.any { }
    myList.none { }
    myList.all { }

    myList.partition { } // Pair<List<T>, List<T>>

I threw in partition as a fun little gem - it's a method that takes in a condition, and returns a list of items that meet the condition, and a list of items that don't. I encourage you to read through the collections documentation, you're bound to find a unique method you've never seen before but does something you've needed before.

Language Features

This section is to discuss things you can do in the Kotlin language that we weren't able to do in Java, and how we can leverage them for additional productivity.

Higher Order Functions

Okay, so this may or may not be a "hidden gem" by your standards, but it depends on how they were presented to you. According to the documentation, "Lambda expressions and anonymous functions are 'function literals', i.e. functions that are not declared, but passed immediately as an expression." However, when I look at code below (from a Java developer's point of view) it's hard to understand what's happening. I was told that a lambda is a function going into another function, but I don't see any parantheses here that I'm passing things into:

    val evenNumbers = myNumberList.filter {
        it % 2 == 0
    }

What's really happening is a benefit of this key point:

In Kotlin, there is a convention that if the last parameter of a function accepts a function, a lambda expression that is passed as the corresponding argument can be placed outside the parentheses.

If we look at the source code for filter, we can see it takes in a function that accepts a type T (whatever type of item is in your list), and outputs a boolean:

    public inline fun <T> Array<out T>.filter(predicate: (T) -> Boolean): List<T> {
        return filterTo(ArrayList<T>(), predicate)
    }

    val evenNumbers = myNumberList.filter {
        it % 2 == 0
    }

So, let's go over an example use case for this. Let's consider writing a method that checks if the user has granted permission to write to storage, and if they have we want to perform some callback:

    private fun withWritePermission(callback: () -> Unit) {
        activity?.let { activity ->
            RxPermissions(activity)
                    .request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    .subscribe { granted ->
                        if (granted) {
                            callback()
                        }
                    }
        }
    }

Since our function only has one parameter, which is another function, we can call this with a pretty cool lambda:

    withWritePermission {
        launchGallery()
    }

I have also seen examples that show this for checking API versions on Android:

    supportsLollipop {
        doSomething()
    }

However, there's a caveat to be aware of here. At Google IO, Jake Wharton explained why they would not include a method like that in the Android KTX library. The reason being, we don't have any way to include an else block above. If functionality changes in a future version of Android, and you need to consider three stages: before lollipop, after lollipop, and after this new change, you wouldn't be able to do that with the above.

So just be careful where you use this last parameter trick, and try not to code yourself into a corner.

Destructuring Declarations

The last powerful Kotlin feature we're going to talk about here is destructuring, which is definitely something that does not get enough credit.

Consider a use case where you have an array of integers representing 3D coordinates. In our old Java way, we'd have to reference each individual index to get what we want:

    val coordinates = arrayOf(5, 10, 15)
    val x = coordinates[0]
    val y = coordinates[1]
    val z = coordinates[2]
    println("X coordinate: $x")
    println("Y coordinate: $y")
    println("Z coordinate: $z")

However, thanks to destructuring, we can do all of that in one line in Kotlin. All you have to do is define the values you want, and wrap each variable name inside parentheses and comma separate them:

    val coordinates = arrayOf(5, 10, 15)
    val (x, y, z) = coordinates
    println("X coordinate: $x")
    println("Y coordinate: $y")
    println("Z coordinate: $z")

A cool use case for this is destructuring a data class so that you can return two items from a function:

    data class Result(val result: Int, val status: Status)
    fun function(...): Result {
        return Result(result, status)
    }

    val (result, status) = function(...)

You can also destructure a map entry, which makes traversing a map a lot nicer. You no longer need to access entry.key and entry.value, you can just destructure inside your loop:

    val actionsMap: Map<String, Action> = hashMapOf(...)
    for ((key, action) in actionsMap) {
        // ...
    }

The way this works under the hood is by adding a componentN() function for each field you want to destructure. This happens automatically for us with data classes, but if you want to do this on your own class, you need to write your own operator functions:

    class Person(val name: String, val age: Int) {
        operator fun component1(): String {
            return name
        }

        operator fun component2(): Int {
            return age
        }
    }

    val person = Person("Adam", 25)
    val (name, age) = person

You can have as many componentN() functions as you need, but if you try to destructure more items than you have component functions for, you'll get an error.

Conclusion

There are three main takeaways from this post:

  1. The Strings and Collections classes are way more powerful in Kotlin than you think. Take some time to read through their documentation, as well as other common classes, and you're bound to be surprised with methods you had no idea existed.
  2. Having function types is really great, and you can do some pretty fancy things with them. There's even more in the docs. Just be aware of some limitations that make it difficult to move forward.
  3. Destructuring is one of Kotlin's lesser known secrets. You can save yourself a lot of time with it!

Kotlin is full of hidden secrets, even beyond everything that was discussed here. Do you have a favorite gem? Reach out to me on Twitter! Want to join me at OkCupid? We're hiring!