Defaulting to Kotlin

Erik Wolfe | 09/29/2017

If you’ve been active in Android over the last couple years, you’ve probably heard about Kotlin here and there, read some docs on it, and said “that looks cool.” Of course, the idea of adopting a new language in a codebase is a daunting one, and one that shouldn’t be made lightly, so the passing interest makes sense in most cases. However, with the official support from Google and the undeniable benefits of the language, now’s the time to dive in and make Kotlin your go-to language on Android. We’ve recently made the move to default to Kotlin at Tack because it’s officially supported; it’s fun, safe, and familiar to write; it reduces dev time and boilerplate, and it decreases external dependencies.

It’s Officially Supported

In May 2017 at Google I/O, Google announced official support for Kotlin. While this was still a surprise for many, it served to vindicate those in the Android community who had already adopted the language. Adoption had been taking off leading up to the announcement and was already being widely-used in Android projects at popular companies (Square, Basecamp, Uber). Just as functional paradigms are probably here to stay on Android, Kotlin will remain the prefered language for the foreseeable future (but of course Java will remain the “default” language…although Android Studio 3.0 allows you to create a new Kotlin project, so what is “default” anyway?).

Before The Announcement

There were many reasons not to fully adopt Kotlin before the announcement: fear of continued support from the community, poor upgrade paths as tooling changes, legacy maintainability for a niche developer preference (see [Clojure, Groovy, Scala, etc.] for Android), and poor documentation and Google support. All of these reasons were invalidated on May 17, 2017. As of that I/O announcement, Google has been working hard to update their documentation and example code to use Kotlin (and continue to use Java, of course). The barrier-to-entry for new Android developers and those looking to convert to Kotlin continues to get lower as documentation gets better.

Documentation

Documentation only takes a developer so far. Many developers want to hit the ground running and get to the “fun stuff” quickly. Because Kotlin was developed by the IDE creators (JetBrains), tooling is top notch. With a few keystrokes: cmd+shift+option+k (or ctrl+alt+shift+k for you Linux/PC folks), or Code > Convert Java File to Kotlin File), a .java file can be converted to a .kt file. While the resulting file may not be idiomatic, it’s a great place to start learning, and set the stage for further experimentation.

Kotlin and Java are Interoperable

Probably the best reason to start using Kotlin today is that Kotlin and Java are interoperable. This means that Kotlin code can live in harmony with your current Java code and you can migrate to, or use your favorite features of, Kotlin as you see fit. We want to make this clear: you and your team can start using Kotlin today in your projects without having to commit to a full re-write or organize the code in a way that keeps Java and Kotlin code separated. You can adopt the language at whatever pace works for you and your organization. At Tack, we had two successful approaches to adding Kotlin: one team started with converting utility classes and Realm models, and another team started converting view models. Both teams were able to implement small portions of Kotlin without affecting the larger codebase, and when comfortable, began writing new features in Kotlin.

It’s Actually Fun and Safe(r) to Write

Beauty Without Effort

You’ve probably read it plenty of times: Kotlin is a joy to write. You know that feeling you get as you create “the perfect method” that solves some problem elegantly, but is easy to understand? Well you’ll get that feeling more often with Kotlin (hopefully). Here’s a bit of a contrived example to describe what I mean:

Given a list of users and an age, we need all the users of that age. A pretty typical way to write this in Java might be:

public List<User> getUsersOfAge(List<User> users, int age) {
    List<User> filteredUsers = new ArrayList<>();
    for (User user : users) {
        if (user.getAge() == age) {
            filteredUsers.add(user);
        }
    }
    return filteredUsers;
}

This is reasonable Java: it’s easy to understand, doesn’t side-effect, and has a good name.

In Kotlin, we may write it this way:

fun getUsersOfAge(users: List<User>, age: Int): List<User> {
   return users.filter { it.age == age }
}

Or a bit more idiomatically:

fun getUsersOfAge(users: List<User>, age: Int ) = users.filter { it.age == age }

Notice we were able to remove the curly braces, and replace the body with a single expression and implicit return. We’ve also kept all the benefits of the Java example, but with fewer lines of code, and a more readable syntax. “Take users, filter the users based on this criteria, return those users” – it has a good flow and reads nicely.

Another simple example is string concatenation:

In Java:

public String buildString(String name, int age) {
    return "Hello, I'm " + name + " and I'm " + age + " years old.";
}

In Kotlin:

fun buildString(name: String, age: Int): = "Hello, I'm $name and I'm $age years old."

We don’t save many lines of code (LOC), but that’s not all it’s about – it’s about readability and maintainability in this case. The Kotlin method is easier to understand because it reads like simple English.

And don’t forget, Kotlin’s interoperable with Java. For example, here we create a comparator in Kotlin, and reference it from a Java class (don’t mind the !! and User?, we’ll cover those next):

class AgeComparator : Comparator<User> {
   override fun compare(o1: User?, o2: User?): Int {
       return Integer.compare(o1!!.age, o2!!.age)
   }
}

that’s referenced from Java:

public List<User> getUsersByAge(List<User> users) {
   Collections.sort(users, new AgeComparator());
   return users;
}

Null Safety

You get it, Kotlin has some sugar that makes it more friendly to developers than good ol’ Java. But maybe that’s not reason enough to take on the change. Let’s not forget Kotlin also makes our code safer! We have @Nullable and @NonNull annotations in Java to remind us to check for null if we need to. But nullability isn’t something an object knows about. It has a context-specific meaning that can be changed from method to method, but it’s a cognitive load the developer carries. Kotlin offers null safety that allows us to explicitly say if an object can be null or not. This means no more surprise NPEs in our code (ideally), and we can safely delegate the cognitive load to the IDE. Compilation will fail if a non-null value is set to null or if a nullable value is not checked in some way (eg: !!. to say “I know this won’t be null, or ?. to say, “if this isn’t null do the next thing”).

Let’s consider the following examples :

val user : User? = null
user.age

fails to compile because we don’t check user for null in user.age, even though we explicitly say it’s nullable with User?.

val user : User? = null
user?.age

compiles because we check user for nullability before accessing a property: user?.age

val user : User = null

fails to compile because we’re setting a value to null that can’t be null. If we changed User = null to User? = null then it’d compile.

val user : User = User()
user?.age

conveniently produces a lint warning because we’re needlessly checking for null with user?.age even though user cannot be null.

These seem like little benefits on the surface, but they make the code much more explicit and easier to manage because developers are left with no question about the nullability of an object. It should be noted that if Java code is converted to Kotlin and it doesn’t have null annotations, then Kotlin will assume the value is not nullable. This adds even more reason to take advantage of the Support Annotations. For your codebase, you can use Android Studio’s Analyze > Inspect Code and run a nullability analysis that can automagically add the annotations for you. If converting code that calls into the platform or a library, check the javadoc to make sure you handle a potential null.

Immutability

Another reason Kotlin is/can be safer than Java is immutability. Like nullability, mutability is neither a new concept nor one that hasn’t been addressed many different ways. Also like nullability, Kotlin has a solution that reduces the developer’s cognitive load and moves the brunt of it to the compiler and tools.

Immutable objects are those whose state cannot be changed after creation. When an object is immutable we gain a few benefits immediately: thread safety, no side-effecting, and predictive state. In Java, immutable objects are typically created with constructor injection of final values and no setters, or a builder. Of course there are plenty of libraries that make this creation a bit easier (remember how verbose Java can be?), like AutoValue and Guava – and we can still use those because Java and Kotlin are interoperable; however, in Kotlin, there are also more idiomatic options like a data class:

data class User (val firstName: String, val lastName: String, val dob: Date, val location: Location?, val age: Int = 0)

A few things to note about the above snippet: when we preface our class with the data keyword we get our getters (no setters of course because our arguments are val for value and not var for variable), a copy method for free (valuable when working with immutable data that may need to be updated), and Kotlin gives use default values too. We’ll jump more into the benefits of a data class in a bit, but suffice it to say, this single line gets a lot of work done for us.

Another precaution Java devs usually take is to do a defensive wrap of a collection before exposing it:

public List<User> getUsers() {
   return new ArrayList(users);
}

This protects the internal state of the class and exposes the data, and, at the same time prevents it from being changed without us “knowing” (side-effect). In Kotlin, we should be explicit about how we want the data to be used:

private val users: MutableList<User> = mutableListOf(user1, user2, user2)
fun getUsers() : List<User> {
   return users.toList()
}

In this case we start with an internal mutable list, but when we expose it, we call toList() to copy the data to a new list and return that. This, like the string building example, doesn’t save LOC, but it creates a clearer intention in the code. Kotlin provides explicitly mutable versions lists, maps, and sets.

It Decreases Development Time and Boilerplate

As Java developers, many of us have gotten used to verbose code. Most of us use tools to reduce that code (Butterknife, AutoValue, Data Binding, Retrolambda, etc.), but Kotlin provides some great functionality out of the box and through some nifty extensions that may make those libraries redundant for your project. Luckily coming from Java, the spin-up time with Kotlin is very short, so there’s little up-front investment needed to experience Kotlin’s succinct, easy-to-write-easy-to-maintain code (read: not having to learn a new library).

We’ve mentioned LOC a couple times, and in both cases the Kotlin solution didn’t reduce LOC much. While LOC is not a valuable metric in many cases, it’s worth evaluating when talking about the time a developer may take to implement a feature, or another developer to grok that feature later. If you’re passionate about reducing LOC, you’re going to love these next couple features, especially data classes.

Lambdas

Lambdas are one of the main reasons devs will see a reduction in LOC. The example we’ve all seen is:

EditText editTextView = (EditText) findViewById(R.id.editText_view);
editText.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       Log.d("BOOM", "Do something crazy!");
   }
});

Becomes:

val editText = findViewById<View>(R.id.editText_view) as EditText
editText.setOnClickListener { Log.d("BOOM", "Do something crazy!") }

And if you want to reference the view:

button.setOnClickListener { v ->  Log.d("BOOM", "Do something crazy!") }

Imagine what this does if you’re using something like RxJava! You’re probably using Retrolambda to keep your RxJava clean for this exact reason.

Android Extensions

To build off the previous example, using the Kotlin Android Extensions, we can reduce our code even more to:

editText_view.setOnClickListener{ Log.d("BOOM", "Do something crazy!") }

With Android Extensions, we can reference a view by its xml ID and not have to worry about any casting or typing findViewById. Again: Kotlin allows us to easily write succinct code without much overhead.

Extension Functions

Another nifty feature of Kotlin that allows us to decrease boiler plate code a bit is extension functions. These are basically methods we can define for existing classes and call them as if they were part of the class in question. For example maybe we have a util method to add some label to a string:

public static String addLabel(String value, String label) {
   return value + " " + label;
}

And the call site looks like:

StringUtil.addLabel(value, "lbs")

With Kotlin our method could look like this:

fun String.addLabel(label: String): String = this + " $label"

And the call site:

value.addLabel("lbs")

The benefit here is that we can call our util methods on the object instead of passing the object in as an argument. This, once again, allows us to have a clearer intention in our code, but it also allows us to clean up all those static *Util classes and their associated call sites. The goal of extension functions is to remove that ugly class reference and method call and replace it with something that reads better.

A positive side-effect of this is that it should reduce the *Util.java explosion that happens when devs can’t find existing utilities. For example someone creates a StringUtils.java and another person creates StringUtil.java – ideally that’s found in review and corrections are made, but we’ve all been on teams where that slips through the cracks. With extension functions, the utility method will be available on all String objects.

Data Classes

No Kotlin blog post would be complete without a reference to data classes. If you’re familiar with AutoValue, then you’ll understand the benefits immediately, but here’s a typical example of how Kotlin can reduce both development time and boilerplate:

A User in Java:

public class User {

   private String firstName;
   private String lastName;
   private Date dob;
   @Nullable
   private Location location;
   private int age;

   public String getFirstName() {
       return firstName;
   }

   public void setFirstName(String firstName) {
       this.firstName = firstName;
   }

   public String getLastName() {
       return lastName;
   }

   public void setLastName(String lastName) {
       this.lastName = lastName;
   }

   public Date getDob() {
       return dob;
   }

   public void setDob(Date dob) {
       this.dob = dob;
   }

   @Nullable
   public Location getLocation() {
       return location;
   }

   public void setLocation(Location location) {
       this.location = location;
   }

   public int getAge() {
       return age;
   }

   public void setAge(int age) {
       this.age = age;
   }

   @Override
   public String toString() {
       return "User{" +
               "firstName='" + firstName + '\'' +
               ", lastName='" + lastName + '\'' +
               ", dob=" + dob +
               ", location=" + location +
               ", age=" + age +
               '}';
   }

   @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (!(o instanceof User)) return false;

       User user = (User) o;

       if (age != user.age) return false;
       if (firstName != null ? !firstName.equals(user.firstName) : user.firstName != null)
           return false;
       if (lastName != null ? !lastName.equals(user.lastName) : user.lastName != null)
           return false;
       if (dob != null ? !dob.equals(user.dob) : user.dob != null) return false;
       return location != null ? location.equals(user.location) : user.location == null;
   }

   @Override
   public int hashCode() {
       int result = firstName != null ? firstName.hashCode() : 0;
       result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
       result = 31 * result + (dob != null ? dob.hashCode() : 0);
       result = 31 * result + (location != null ? location.hashCode() : 0);
       result = 31 * result + age;
       return result;
   }
}

And the same User in Kotlin:

data class User (val firstName: String, val lastName: String, val dob: Date, val location: Location? = null, val age: Int = 0)

We get an immutable object, public getters, hash/equals, and toString() implemented. We also get the benefit of default arguments, and a copy function.

Default Arguments and Named Arguments

Default arguments allow us do something like this to create a User:

val user = User("FirstName", "LastName", Date())

Notice we don’t pass in a location or age. We don’t need to because they have defaults provided in their absence. For interoperability, we can add @JvmOverloads to our User constructor. This annotation will generate overloads that allow us to take advantage of the default arguments from Java as well. Our User would look like this (adding @JvmOverloads constructor):

data class User @JvmOverloads constructor(val firstName: String, val lastName: String, val dob: Date, val location: Location? = null, val age: Int = 0)

and we can then create an object in Java a couple ways:

new User("First", "Last", new Date());
// or
new User("First", "Last", new Date(), new Location("CO", "Denver"), 1);

When we want to update the user and work from the new instance, all we have to do is copy the object:

val updatedUser = user.copy(location = Location("CO", "Denver"))

That will copy the current user, and set the location to Denver. Notice we don’t have to provide all the parameters and we get a new immutable object with the changes specified. Named arguments make part of this possible.

It Reduces External Dependencies

In many cases, developers will find that the increased method count for Kotlin (~7000 methods) will be offset by the fact that you may be able to remove other dependencies that were previously used. This can be helpful if you’re close to reaching your dex limit.

As you add more Kotlin, you won’t need Retrolambda anymore. Retrolambda is wonderful if working in the pre Java 8 world (welcome to Android), but not needed with Kotlin (see previous section). Depending on what features of Guava are being used, the Kotlin standard library may provide the functionality you need, or can be provided with a simple extension function. If you’re using Butterknife or Data Binding to simplify referencing views, then Kotlin’s Android Extensions will fill that need. Of course both Butterknife and Data Binding provide more functionality than simple view referencing/finding, so your mileage may vary. It’s also worth pointing out that Kotlin has some built-in functional methods (like Java 8), so depending on how you’re currently using RxJava, you may find that converting to Kotlin’s map, filter, etc. methods is reasonable for you.

We’re Defaulting To Kotlin

At Tack, we’re defaulting our Android projects to Kotlin. The barrier-to-entry is ridiculously low for a new, enjoyable language that will help us write better code with fewer external dependencies. What’s not to love? Support for Kotlin is strong today and will continue to get better as the Android documentation improves, not to mention the pending release of Android Studio 3.0 with baked in Kotlin support. All signs point to the language staying relevant and becoming the standard on many projects, so even if your current team can’t/won’t convert, start learning it now because your career and future self will thank you.

Get a Quote