Using Project Kotlin for Android

Statically-typed, highly-interoperable augmentation to the Java language.

Author:        @JakeWharton
Created:        2015-01-20
Updated:        2015-01-26

Note: This is a copy (and slight edit) of a document that was presented internally at Square. It advocates the use of the Kotlin language for developing Android apps.

Table of Contents

Introduction

Features

Interoperability

Lambas

Null Safety

Extension Methods

Data Classes

Other Awesome Things

Use Cases

Utility Methods

RxJava, Listeners

Mock Mode & Test Fixtures

Technical

Feature Implementation

Runtime

Build Integration

IDE Support

Alternatives

Groovy

Scala

JDK 8 + Retrolambda

Wait for Java 8

Risks

New Language

Jack & Jill

Resources

Introduction

Kotlin is a language by JetBrains, the company behind IntelliJ IDEA and other (sweet) tools, and is purpose built for large-scale software projects to improve upon Java with a focus on readability, correctness, and developer productivity.

The language was created in response to limitations in Java which were hindering development of JetBrains' software products and after an evaluation of all other JVM languages proved unsuitable. Since the goal of Kotlin was for use in improving their products, it focuses very strongly on interop with Java code and the Java standard library.

Features

Below are the features which provide the largest value to Android and solve specific, incessant problems that plague client application development. For a comprehensive list of features refer to the official Kotlin reference documentation.

Interoperability

The most important feature of the Kotlin language and runtime is its core focus on interoperability. Unlike other JVM alternative languages (most specifically: Scala), idiomatic Kotlin should be able to easily call Java as well as have idiomatic Java easily call into Kotlin. In fact, you should never know that you are crossing that boundary in either direction.

The runtime of Kotlin exists only to support the language features making it extremely lean. The java standard library types, collections, etc. are all reused and ultimately are augmented for greater utility through some of the subsequently mentioned features.

(See the Kotlin documentation for more.)

Lambas

'nuff said.

(See the Kotlin documentation for more.)

Null Safety

Our best friend null is a first-class citizen in Kotlin's type system. Types are aware of their nullability and it receives special treatment in both flow control and dereferencing.

val x: String? = "Hi"
x.length // Does not compile.

val y: String = null // Does not compile.

Since null is unavoidable, there are many ways to deal with it.

if (x != null) {
 x.
length // Compiles! Not idiomatic just to get length!
}

// Same as above (IntelliJ auto-suggested the change).
x?.
length

// Elvis operator.
val len = x?.length ?: -1

val len = x!!.length // Will throw if null. Rarely used.

Having null in the type system is vastly superior to nullability annotations as one would find in Java. In this case both the IDE and the compiler are strictly enforcing contracts instead of just suggesting.

By pushing nullability into the type system, traditional workarounds like optional are not needed. This is especially important on Android whose heap and garbage collector are much more sensitive to tiny and/or short-lived objects. Java 10 will push Optional onto the stack, but we'll all be dead before Android sees that.

Due to the aforementioned heap and GC concerns, Android uses null a lot. Like a lot a lot. Since all references are nullable in Java, Android code is often littered with unneeded null checks or experiences crashes due to dereferencing something which was not thought to be null. Moving this into the type system will eliminate this ambiguity removing both the noisy, needless checks and a large majority of inadvertent exceptions.

(See the Kotlin documentation for more.)

Extension Methods

In a way similar to C#, Kotlin allows the declaration of both static and instance methods into types which you do not control–including those from the Java standard library. Unlike C#, however, these method are statically resolved using imports which provides clarity on their origin (just as a statically imported helper method on a utility class would).

fun String.last() : Char {
 
return this[length - 1] // Using built-in extension for array indexing!
}


val x = "Hey!"
println(x.last()) // Prints "!".

Half of Guava* and 2/3rds of the Cash app* is comprised of static utility methods. While this wouldn't eliminate their presence, it would allow you to use them directly on the types for which they were implemented. This is a big win for readability. Death to the plurals (Collections), the numbereds (Collections2), and the 'util' suffixeds (StringUtils).

* Not even remotely a true statistic

(See the Kotlin documentation for more.)

Data Classes

Akin to what value types in Java 10 are hoping to accomplish, data classes are those which exist only to group data in a semantic, immutable type. We currently use AutoValue to simulate value types in an easy fashion.

data class Money(val currency: String, val amount: Int)

This class gives you equals, hashCode, toString for free. Each component is exposed by a read-only property of the same name. There is also a copy method for changing values into a new instance.

val money = Money("USD", 100)
val moreMoney = money.copy(amount = 200) // Oh hey named params!

Instances can be unpacked with multi-assignment. This is similar to Python's tuples and has been shimmed into the standard library such as for Map.Entry when looping over a map.

for ((key, value) in map) {
 println(key +
": " + value)
}

(See the Kotlin documentation for more.)

Other Awesome Things

  • Classes and methods are final by default. You can declare them open if you want extensibility.

    class Foo {}
    class Bar extends Foo {} // Does not compile

    open class Foo {}
    class Bar extends Foo {} // Compiles!

    (See the Kotlin documentation for more.)
  • Implicit casting. If a type check succeeds, the variable is now that more specific type.

    val x: Object = "hi"
    val length = x.length // Does not compile

    if (x is String) {
     
    val length = x.length // Compiles!
    }

    Note: this code isn't quite idiomatic!

    This also works for nullability.

    val x: Object? = "Hi"
    if (x != null) {
     
    val y: Object = x // 'x' is a non-null type here
    }

    (See the Kotlin documentation for more.)
  • Properties are super-powered fields and support delegation.

    For example, here's a property (accessible via field-like or method-like means) which only computes the expensive operation once on first access.

    val lazy: String by Delegates.lazy { expensiveOperation() }

    (See the Kotlin documentation for more.)
  • JetBrains is actively working on support of Square projects! Most recently Retrofit and Dagger's runtime were declared as being supported.

    Since Kotlin uses its own compiler, not all features of the Java compiler are supported. As such, using annotation processors (such as
    Dagger and Butter Knife) is not currently supported. A lot of the language features of Kotlin obviate the need for annotation processors, however. For example, I was able to re-implement most of Butter Knife as a runtime library using delegated properties.

Use Cases

Some concrete examples of pain points that exist when developing the Square Cash Android application with Java and how Kotlin's features improve the situation.

Utility MethodsScreen Shot 2015-01-21 at 10.01.23 PM.png

So-called static "utility" methods litter our code bases (some of Cash's pictured). Large chunks of libraries like Guava, commons-lang, etc. exist to provide these methods which simplify common interaction with a specified type.

Kotlin doesn't eliminate these utility methods–it gives them superpowers! Everyone has methods they wish were on built-in types like String.

fun String.truncateAt(max: Int) : String {
 
return substring(0, Math.min(length, max))
}

Calling code gets a more semantically meaningful invocation:

val name = "Jake Wharton".truncateAt(4)

This example is trivial, but anyone who has written Java for a reasonable amount of time can extrapolate as to how much of an impact this will have on the clarity of code.

Another specific example worth noting is the types which are automatically generated from our protocol buffer schema definitions. Even though we own these types in our source tree, the fact that they are generated prevents us from any meaningful means of extending them with useful methods (where you at, partial classes?). Kotlin lifts these utility methods to operate directly on the generated types.

fun Money.add(other: Money): Money {
 
if (currency_code != other.currency_code) {
   
throw IllegalArgumentException("Currency code ${other.currency_code}"
       
+ " does not match ${currency_code}.") // Hey String interpolation!
 }
 
return copy(amount = amount + other.amount)
}

Now we can add Money like we really want to.

val one = Money("USD", 100)
val two = Money("USD", 200)
val three = one.add(two)

But wait, there's more! Order now and you'll also receive operator overloading (just pay separate shipping & handling). If I rename the above method to plus I can now add these instances just like any other value types.

val three = one + two

Bonus: I can put my cursor on the plus sign and hit CMD+B like any other method and be taken directly to the extension method implementation!

Remember, the use of these methods (including the operator overloading example) is contingent on a static import. Unlike C# these are not magically applied everywhere.

RxJava, Listeners

Android applications are inherently asynchronous in nearly everything they do. This results in code which is comprised of callbacks, listeners, and a bit in dealing with RxJava's reactive streams (a powerful mechanism through which we manage asynchronous data flow).

buttonView.setOnClickListener(new OnClickListener() {
 
@Override public void onClick(View v) {
   finish()
;
 
}
})
;

The advantages of lambdas should be general knowledge by now, but Android is unlikely to see Java 8 in the next two years–even then only being usable on the latest version. Kotlin gives us this now and across all versions of Android.

buttonView.setOnClickListener({ finish() })

When you start building up streams of async data with RxJava this can reduce hundreds of lines of code to under 10. The actual behavior of the code isn't changing. It is only the excessive boilerplate being eliminated which come with anonymous classes, long generic type declarations, and verbose creation of intermediate value types.

Mock Mode & Test Fixtures

The type-safe builder DSL is very reminiscent of Groovy (and is called out as such in the docs) and is the majority of how Gradle is configured. Kotlin's version is a big improvement in that it's forced to be type-safe whereas Groovy's is completely dynamic.

While this is unlikely to be of use in the main source of any application, it has desirable features for our debug builds.

"Mock mode" is our development tool in which interactions with external APIs are re-implemented in deterministic, representative, and easily configurable ways. This allows for fast iteration independent of the backend, simplified reproduction of corner cases which are otherwise hard to setup, and known data for writing instrumentation tests against. The data the backs mock mode is written in a variety of ways. Usually this involves creating instances of the proto-generated types in code along with resources like images. The mocked service APIs then reference these instances and resources when creating fake network responses.

In addition to default parameters, this type-safe shorthand would eliminate the triply-nested builders whose methods serve only to distract from the underlying mock data.

payment {
 token = UUID.randomUUID().toString()
 amount = Money(
"USD", 100)
 recipient = JESSE
 sender = JAKE
 orientation = CASH
 role = SENDER
 state = WAITING_ON_SENDER
}

Not only is this an object that represents mock data, but through extension methods this was done directly on the generated Java class from the Payment proto.

Technical

Feature Implementation

With all of the aforementioned (and sexy) language features, performance implications should be a primary concern. Being a JVM language, we can look at the generated bytecode and determine exactly what is happening for each one.

Data classes sadly aren't as efficient as Java 10's value types. We still pay the cost of an object on the heap. We can also see that both extension methods by themselves and via operator overloading are just a simple static method dispatch.

Kotlin

val three = Money("USD", 100) + Money("USD", 200)

 0: new           #14         // class Money

 3: dup

 4: ldc           #16         // String USD

 6: bipush        100

 8: invokespecial #20         // Method Money."<init>":(Ljava/lang/String;I)V

11: new           #14         // class Money

14: dup

15: ldc           #16         // String USD

17: sipush        200

20: invokespecial #20         // Method Money."<init>":(Ljava/lang/String;I)V

23: invokestatic  #26         // Method examplePackage$6fdc46c0.plus:(LMoney;LMoney;)LMoney;

Java

Money three = Moneys.plus(new Money("USD", 100), new Money("USD", 200));

 0: new           #2          // class Money

 3: dup

 4: ldc           #3          // String USD

 6: bipush        100

 8: invokespecial #4          // Method Money."<init>":(Ljava/lang/String;I)V

11: new           #2          // class Money

14: dup

15: ldc           #3          // String USD

17: sipush        200

20: invokespecial #4          // Method Money."<init>":(Ljava/lang/String;I)V

23: invokestatic  #5          // Method Moneys.plus:(LMoney;LMoney;)LMoney;

Data class implementation does not differ much from AutoValue or a hand-rolled value type. The only notable differences is the addition of the copy helper and the number-based accessor methods for value unpacking.

Kotlin

data class Money(val currency: String, val amount: Int)

public final class Money implements kotlin.jvm.internal.KObject {

  public static final kotlin.reflect.jvm.internal.KClassImpl $kotlinClass;

  static {};

    Code:

       0: ldc           #2                  // class Money

       2: invokestatic  #91                 // Method kotlin/reflect/jvm/internal/InternalPackage.kClassFromKotlin:(Ljava/lang/Class;)Lkotlin/reflect/jvm/internal/KClassImpl;

       5: putstatic     #93                 // Field $kotlinClass:Lkotlin/reflect/jvm/internal/KClassImpl;

       8: return

  public final java.lang.String getCurrency();

    Code:

       0: aload_0

       1: getfield      #19                 // Field currency:Ljava/lang/String;

       4: areturn

  public final int getAmount();

    Code:

       0: aload_0

       1: getfield      #27                 // Field amount:I

       4: ireturn

  public Money(java.lang.String, int);

    Code:

       0: aload_1

       1: ldc           #32                 // String currency

       3: invokestatic  #38                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V

       6: aload_0

       7: invokespecial #40                 // Method java/lang/Object."<init>":()V

      10: aload_0

      11: aload_1

      12: putfield      #19                 // Field currency:Ljava/lang/String;

      15: aload_0

      16: iload_2

      17: putfield      #27                 // Field amount:I

      20: return

  public final java.lang.String component1();

    Code:

       0: aload_0

       1: getfield      #19                 // Field currency:Ljava/lang/String;

       4: areturn

  public final int component2();

    Code:

       0: aload_0

       1: getfield      #27                 // Field amount:I

       4: ireturn

  public final Money copy(java.lang.String, int);

    Code:

       0: aload_1

       1: ldc           #32                 // String currency

       3: invokestatic  #38                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V

       6: new           #2                  // class Money

       9: dup

      10: aload_1

      11: iload_2

      12: invokespecial #46                 // Method "<init>":(Ljava/lang/String;I)V

      15: areturn

  public static Money copy$default(Money, java.lang.String, int, int);

    Code:

       0: aload_0

       1: iload_3

       2: iconst_1

       3: iand

       4: ifeq          12

       7: aload_0

       8: getfield      #19                 // Field currency:Ljava/lang/String;

      11: astore_1

      12: aload_1

      13: iload_3

      14: iconst_2

      15: iand

      16: ifeq          24

      19: aload_0

      20: getfield      #27                 // Field amount:I

      23: istore_2

      24: iload_2

      25: invokevirtual #50                 // Method copy:(Ljava/lang/String;I)LMoney;

      28: areturn

  public java.lang.String toString();

    Code:

       0: new           #55                 // class java/lang/StringBuilder

       3: dup

       4: invokespecial #56                 // Method java/lang/StringBuilder."<init>":()V

       7: ldc           #58                 // String Money(currency=

       9: invokevirtual #62                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      12: aload_0

      13: getfield      #19                 // Field currency:Ljava/lang/String;

      16: invokevirtual #62                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      19: ldc           #64                 // String , amount=

      21: invokevirtual #62                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      24: aload_0

      25: getfield      #27                 // Field amount:I

      28: invokevirtual #67                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;

      31: ldc           #69                 // String )

      33: invokevirtual #62                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      36: invokevirtual #71                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

      39: areturn

  public int hashCode();

    Code:

       0: aload_0

       1: getfield      #19                 // Field currency:Ljava/lang/String;

       4: dup

       5: ifnull        14

       8: invokevirtual #74                 // Method java/lang/Object.hashCode:()I

      11: goto          16

      14: pop

      15: iconst_0

      16: bipush        31

      18: imul

      19: aload_0

      20: getfield      #27                 // Field amount:I

      23: iadd

      24: ireturn

  public boolean equals(java.lang.Object);

    Code:

       0: aload_0

       1: aload_1

       2: if_acmpeq     50

       5: aload_1

       6: instanceof    #2                  // class Money

       9: ifeq          52

      12: aload_1

      13: checkcast     #2                  // class Money

      16: astore_2

      17: aload_0

      18: getfield      #19                 // Field currency:Ljava/lang/String;

      21: aload_2

      22: getfield      #19                 // Field currency:Ljava/lang/String;

      25: invokestatic  #80                 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z

      28: ifeq          52

      31: aload_0

      32: getfield      #27                 // Field amount:I

      35: aload_2

      36: getfield      #27                 // Field amount:I

      39: if_icmpeq     46

      42: iconst_0

      43: goto          47

      46: iconst_1

      47: ifeq          52

      50: iconst_1

      51: ireturn

      52: iconst_0

      53: ireturn

}

Java

@AutoValue

public abstract class Money {

  public abstract String currency();

  public abstract int amount();

  public static Money of(String currency, int amount) {

    return new AutoValue_Money(currency, amount);

  }

}

final class AutoValue_Money extends Money {

  AutoValue_Money(java.lang.String, int);

    Code:

       0: aload_0

       1: invokespecial #1                  // Method Money."<init>":()V

       4: aload_1

       5: ifnonnull     18

       8: new           #2                  // class java/lang/NullPointerException

      11: dup

      12: ldc           #3                  // String Null currency

      14: invokespecial #4                  // Method java/lang/NullPointerException."<init>":(Ljava/lang/String;)V

      17: athrow

      18: aload_0

      19: aload_1

      20: putfield      #5                  // Field currency:Ljava/lang/String;

      23: aload_0

      24: iload_2

      25: putfield      #6                  // Field amount:I

      28: return

  public java.lang.String currency();

    Code:

       0: aload_0

       1: getfield      #5                  // Field currency:Ljava/lang/String;

       4: areturn

  public int amount();

    Code:

       0: aload_0

       1: getfield      #6                  // Field amount:I

       4: ireturn

  public java.lang.String toString();

    Code:

       0: new           #7                  // class java/lang/StringBuilder

       3: dup

       4: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V

       7: ldc           #9                  // String Money{currency=

       9: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      12: aload_0

      13: getfield      #5                  // Field currency:Ljava/lang/String;

      16: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      19: ldc           #11                 // String ,

      21: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      24: ldc           #12                 // String amount=

      26: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      29: aload_0

      30: getfield      #6                  // Field amount:I

      33: invokevirtual #13                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;

      36: ldc           #14                 // String }

      38: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      41: invokevirtual #15                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

      44: areturn

  public boolean equals(java.lang.Object);

    Code:

       0: aload_1

       1: aload_0

       2: if_acmpne     7

       5: iconst_1

       6: ireturn

       7: aload_1

       8: instanceof    #16                 // class Money

      11: ifeq          50

      14: aload_1

      15: checkcast     #16                 // class Money

      18: astore_2

      19: aload_0

      20: getfield      #5                  // Field currency:Ljava/lang/String;

      23: aload_2

      24: invokevirtual #17                 // Method Money.currency:()Ljava/lang/String;

      27: invokevirtual #18                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z

      30: ifeq          48

      33: aload_0

      34: getfield      #6                  // Field amount:I

      37: aload_2

      38: invokevirtual #19                 // Method Money.amount:()I

      41: if_icmpne     48

      44: iconst_1

      45: goto          49

      48: iconst_0

      49: ireturn

      50: iconst_0

      51: ireturn

  public int hashCode();

    Code:

       0: iconst_1

       1: istore_1

       2: iload_1

       3: ldc           #20                 // int 1000003

       5: imul

       6: istore_1

       7: iload_1

       8: aload_0

       9: getfield      #5                  // Field currency:Ljava/lang/String;

      12: invokevirtual #21                 // Method java/lang/String.hashCode:()I

      15: ixor

      16: istore_1

      17: iload_1

      18: ldc           #20                 // int 1000003

      20: imul

      21: istore_1

      22: iload_1

      23: aload_0

      24: getfield      #6                  // Field amount:I

      27: ixor

      28: istore_1

      29: iload_1

      30: ireturn

}

Lambdas have a few forms–all of which generate a class. For non-capturing lambdas, Kotlin can use a single static instance rather than having to create an instance each time. This makes pure functional programming very efficient. This example illustrates the interop with Java's interfaces for callbacks and not Kotlin's support for function types as method parameters.

Kotlin

t.setListener(Listener { println("Thing!") })

      15: getstatic     #30                 // Field _DefaultPackage$Testing$64131055$main$1.INSTANCE$:L_DefaultPackage$Testing$64131055$main$1;

      18: checkcast     #32                 // class example/Listener

      21: invokevirtual #36                 // Method Thing.setListener:(Lexample/Listener;)V

Java

thing.setListener(new Listener() {

  @Override public void onThing() {

    System.out.println("Thing!");

  }

});

       9: new           #4                  // class Main$1

      12: dup

      13: invokespecial #5                  // Method Main$1."<init>":()V

      16: invokevirtual #6                  // Method Thing.setListener:(LThing$Listener;)V

Map iteration shows multi-assignment value unpacking in action in Kotlin. Rather than wrap the iterator and Map.Entry instances, Kotlin delegates to static helper methods for shimming it into the for-each language feature which avoids needless allocations. The pointless loop contents are grayed out.

Kotlin

for ((key, value) in map) {

  println(key + ": " + value)

}

      16: aload_1

      17: invokestatic  #35                 // Method kotlin/KotlinPackage.iterator:(Ljava/util/Map;)Ljava/util/Iterator;

      20: astore_3

      21: aload_3

      22: invokeinterface #41,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z

      27: ifeq          89

      30: aload_3

      31: invokeinterface #45,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;

      36: checkcast     #47                 // class java/util/Map$Entry

      39: astore_2

      40: aload_2

      41: invokestatic  #51                 // Method kotlin/KotlinPackage.component1:(Ljava/util/Map$Entry;)Ljava/lang/Object;

      44: checkcast     #53                 // class java/lang/String

      47: astore        4

      49: aload_2

      50: invokestatic  #56                 // Method kotlin/KotlinPackage.component2:(Ljava/util/Map$Entry;)Ljava/lang/Object;

      53: checkcast     #53                 // class java/lang/String

      56: astore        5

      58: new           #58                 // class java/lang/StringBuilder

      61: dup

      62: invokespecial #62                 // Method java/lang/StringBuilder."<init>":()V

      65: aload         4

      67: invokevirtual #66                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      70: ldc           #68                 // String :

      72: invokevirtual #66                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      75: aload         5

      77: invokevirtual #66                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      80: invokevirtual #72                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

      83: invokestatic  #78                 // Method kotlin/io/IoPackage.println:(Ljava/lang/Object;)V

      86: goto          21

Java

for (Map.Entry<String, String> entry : map.entrySet()) {

  System.out.println(entry.getKey() + ": " + entry.getValue());

}

       4: aload_1

       5: invokeinterface #3,  1            // InterfaceMethod java/util/Map.entrySet:()Ljava/util/Set;

      10: invokeinterface #4,  1            // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator;

      15: astore_2

      16: aload_2

      17: invokeinterface #5,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z

      22: ifeq          91

      25: aload_2

      26: invokeinterface #6,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;

      31: checkcast     #7                  // class java/util/Map$Entry

      34: astore_3

      35: aload_3

      36: invokeinterface #8,  1            // InterfaceMethod java/util/Map$Entry.getKey:()Ljava/lang/Object;

      41: checkcast     #9                  // class java/lang/String

      44: astore        4

      46: aload_3

      47: invokeinterface #10,  1           // InterfaceMethod java/util/Map$Entry.getValue:()Ljava/lang/Object;

      52: checkcast     #9                  // class java/lang/String

      55: astore        5

      57: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;

      60: new           #12                 // class java/lang/StringBuilder

      63: dup

      64: invokespecial #13                 // Method java/lang/StringBuilder."<init>":()V

      67: aload         4

      69: invokevirtual #14                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      72: ldc           #15                 // String :

      74: invokevirtual #14                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      77: aload         5

      79: invokevirtual #14                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      82: invokevirtual #16                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

      85: invokevirtual #17                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

      88: goto          16

Runtime

It is worth comparing some interesting tidbits about the Kotlin runtime vs. other libraries in use. Android has a hard limit of 65k methods and 65k fields in a single dex file.

Library

Jar Size

Dex Size

Method Count

Field Count

kotlin-runtime-0.10.195

354 KB

282 KB

1071

391

kotlin-stdlib-0.10.195

541 KB

835 KB

5508

458

While we are not using ProGuard (and have no plans to at this time), both of the Kotlin libraries respond well to unused code stripping. An example can be seen in the "Size" section of this post evaluating Kotlin for Android by Mike Gouline.

Most libraries in this list are already included in every Android app we make. This list is not exhaustive for some apps, but it represents the general foundation on which we build our apps.

Library

Jar Size

Dex Size

Method Count

Field Count

rxjava-1.0.4

678 KB

513 KB

3557

1668

support-v4-21.0.3

745 KB

688 KB

6721

1886

play-services-base-6.5.87

773 KB

994 KB

5212

2252

okio-1.2.0

54 KB

55 KB

508

76

okhttp-2.2.0

304 KB

279 KB

1957

882

retrofit-1.9.0

119 KB

93 KB

766

228

picasso-2.4.0

112 KB

97 KB

805

342

dagger-1.2.2

59 KB

54 KB

400

119

butterknife-6.0.0

48 KB

50 KB

307

73

wire-runtime-1.6.1

71 KB

71 KB

471

147

gson-2.3.1

206 KB

170 KB

1231

390

Total

2963 KB

2894 KB

21935

8063

A comparison of the runtimes of competing alternative JVM languages. And Guava <3!

Library

Jar Size

Dex Size

Method Count

Field Count

scala-library-2.11.5

5.3 MB

4.9 MB

50801

5820

groovy-2.4.0-grooid

4.5 MB

4.5 MB

29636

8069

guava-18.0

2.2 MB

1.8 MB

14833

3343

Build Integration

All our apps build with Google's new Gradle-based build system. JetBrains publishes a first-party Gradle plugin for adding Kotlin to Android projects.

Aside from declaring the dependencies, it's one line in your build file:

apply plugin: 'kotlin-android'

Once those are applied you can put your Kotlin files in src/main/kotlin/ and src/androidTest/kotlin/ and they will be compiled as part of a normal build.

IDE Support

All our apps use Google's fork of IntelliJ IDEA called Android Studio. Since it's a fork, it inherits all the niceties of IntelliJ IDEA including the fact that the authors of the IDE, JetBrains, happened to author Kotlin and use it to build said IDE.

Because of this fact, support is top notch. Basically on par with Java as far as static analysis, code completion, and debugging.

Alternatives

Screen Shot 2015-01-21 at 1.02.20 PM.png

Groovy

  • Much less type-safe due to a high-level of dynamism. Types are often masked from static analysis due to implicit property use and closures.
  • Additionally, Groovy allows for intercepting method-not-found events and recovering from them which is entirely a runtime procedure.
  • We experience this a lot with our build scripts and Gradle plugins. You can either explicitly specify types where it can't figure them out or reduce yourself to slightly non-idiomatic Groovy.
  • Compiler defers a lot of validation to runtime without explicit @CompileStatic on every class. Because we are targeting a resource constrained environment we need this everywhere but it's prone to human error of omission.
  • Very easy to integrate into our build system.
  • Runtime performance reported as having coldspots on Android, even with static compilation.

More reading:

Scala

  • Can literally do anything and everything and likely in more than one way.
  • Large runtime in size and method/field count. There are hand-tuned, pre-dexed versions but it requires build complexity to link against one runtime and package another.
  • Slower compilation times in general. (See: sbt note below)
  • Requires strong style guide and discipline which is a much larger cognitive load than any other viable JVM alternative language.
  • Needing a "The Good Parts" is a bad part (although these good parts do make it good).
  • Readability often suffers greatly because of the extremely flexible syntax and dynamic extensibility. Libraries often optimize for terseness and ease of writing but we know that reading and understanding are much more important.
  • Difficult to integrate with the Gradle-based build system. We would probably have to switch to sbt which comes with a reduction in features and lack of support from Google, but would likely eliminate the speed problem.
  • We also get some cool features that Gradle doesn't have like continuous incremental compilation, test, and deployment. This is wicked-cool to watch in action.
  • IntelliJ IDEA plugin is very spotty and breaks frequently. Often the language is too complex or opaque to be understood statically and IDE inference is reduced or completely absent.

JDK 8 + Retrolambda

  • Allows use of lambdas and method references. Uses opaque bytecode rewriting to transform them into static methods and anonymous classes which call them.
  • No other Java 8 language features or any standard library features supported (streams, etc.) nor can you use almost any library targeting Java 8.
  • The code in your IDE does not match the byte code that is running on your device. Can cause problems in the IDE as a result, specifically when debugging.

Wait for Java 8

  • Google has not committed to supporting the Java 8 language features and standard library changes for Android.
  • Currently Java 7 is partially supported on Android 4.4 and more so on Android 5.0, but it is not complete.
  • Some language features have been able to be backported since they are only syntactical sugar (switch on strings, diamond operator). It is unlikely many Java 8 language features would be able to get this treatment.
  • We have some back-channel hints and connections to those working on this at Google and the takeaway is waiting for Java 8 would not be wise. Besides, even if it launched tomorrow, see the next point:
  • All of the standard library changes would only be available on the version of Android that introduced Java 8 and up (say, Android 5.1 "lulzipop"). It would be about 2 years before we would be able to use them for apps due to device distribution and our minimum supported version of Jelly Bean.
  • This is very much akin to Android 4.x's launch which took two years before most apps were able to make the full jump.
  • Despite being arguably the largest step forward in Java's history (rivaling 1.5), the language and runtime features are not on par with the alternative languages. The next version of Java that would really be advantageous would be 10 (value types, generics over value types, underscore operator, reactive streams interfaces in JDK).

Risks

New Language

Kotlin takes the Java skills that you already have and makes them more effective. Like Groovy, Kotlin has a very low learning curve and most people can pick up a majority of the language in a single day. Unlike Groovy, however, Kotlin tries very hard to prevent you from shooting your foot off while still remaining an enhancement to Java.

It will take time until best practices start to tease themselves out. Most developers are aware of subtleties of the Java language which are important to keep in mind when working on Android. The for-each loop in Java is a good example. The implicit Iterator allocation is something that we want to avoid in certain areas of code (libraries, hot pathways like drawing callbacks). These subtleties exist in Kotlin and will take some time to get used to.

We know that readability is important and despite some deviations from Kotlin almost everything should have recognizable behavior–even without prior knowledge. Additionally, almost all code should have its readability improved in clarity of type-safety, null-safety, and boilerplate reduction.

Unlike a brand new language such as Go, Kotlin's enhancement to Java and ability to coexist won't have to affect recruiting or hiring practices. Its use is something that we should make Android candidates aware of, and has the potential to viewed as an enticing quality.

Jack & Jill

The tools team at Google is currently working on a replacement for javac and dx which combines them into a single step. Among other toolchain changes around how library projects are managed, this is meant to dramatically reduce build times as these are two of the top slowest parts of the Android build.

Despite these new tools and a new toolchain, .class files will never be something that is unsupported. It would be an unparalleled and unfathomable regression to not allow developers to bring .jar files which are just glorified .zip files of .class files. This practice is so widespread that there is zero chance of it disappearing.

Another interesting point to be made about this new toolchain is that it has the means of unlocking alternative first-party languages. It's hard to imagine that all of the engineering time that is required to build these tools would be committed unless there are more benefits than speed. We do not know if another language is being targeted or even being considered.

We can safely assume that Java will be usable for development at least through the end of this decade and thus so will Kotlin.

Resources