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
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.
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.
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.)
'nuff said.
(See the Kotlin documentation for more.)
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.)
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.)
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.)
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.
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.
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.
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.
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 |
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 |
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.
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.
More reading:
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.
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.