Kotlin production tales
Almost two years have passed since I started droning on about how awesome Kotlin is and how much better it makes Android development.
Back then, however, I made a point of not recommending it for production use. I wanted to play around with it first and wait for a more mature release.
All that changed in late 2015 when 1.0 betas started rolling out regularly, bringing the first stable release closer and convincing me to try converting a production app to Kotlin. A few months later, 1.0 is live and our converted app is in the store.
Let’s start by inserting some names into this story: the production app chosen for conversion is called Safedome. Feel free to read about what it is and what it does in the store listing.
What made Safedome a good candidate for conversion? Large parts of it use RxJava and several attempts were made to go functional with the collections (mostly via quasi-functional operations in Guava), so it was (at the very least) crying out for some lambda support.
Furthermore, many of the views are reasonably complex and were drowning in view binding boilerplate. The Android Extensions looked appealing.
Any drastic changes to production applications are stressful. What if something goes wrong? There’s always a list of potential problems in the back of your mind. Let’s look at the main ones that crossed mine prior to starting the rewrite.
The main difference between personal and work projects is the number of people involved. Sure, you can write a personal app in whatever exotic language you want, but your team (present and future) must be able to understand and contribute to your work apps.
Hence, my team (of one other Android developer) needed to be convinced and trained. Fortunately the convincing part was achieved through the documentation; the benefits are not hard to see.
So how long does it take for a Java developer to learn Kotlin? You need a few days to pick up enough of the basics to start writing Java-like code, and another week or two discover nifty tricks that Java is incapable of. First you’ll get used to the basic syntax differences and then you’ll discover inline lambdas and when clauses. Eventually, after some practice, you’ll be ready to write a whole app in Kotlin.
Reasonable concern: do Java libraries work well with Kotlin? Although it’s claimed to be fully compatible with Java, I dreaded the possibility that one of our core libraries might run into some unexpected problems.
That didn’t happen. Most libraries worked with no additional effort and only one required some further research (it was Realm, but more on that later).
We did remove some libraries, but not because they didn’t work — they were just no longer needed. Everything we used Guava for is included in Kotlin out of the box and Android Extensions replaced Butter Knife (no method annotations, but we can live with that when there are lambdas).
Why aren’t Scala and Groovy normally used for Android? Because method count matters. When 65K is the maximum (ignoring the often problematic multidex support), adding a new library can be daunting.
Below is our current method count with Kotlin’s share on the right — 388 methods. Keep in mind that ProGuard is enabled — so all unused methods are removed — but it’s still impressive that an actual production app, not just some one-screen demo, only needs 388 methods for the whole language to work.
Another interesting detail affecting the method count is how lambdas are implemented compared to Java 8, which has recently become available in Android. In some cases, Kotlin actually generates a more efficient bytecode through the use of inline functions, meaning that the code is inlined at the call site and the method count remains unchanged. As a result, in the worst case it produces the same number of methods as Java 8, in the best (and likely average) case — even lower.
APK size and line count
APK size figures are more encouraging than predicted too — around 50 KB increase. Remember that a few libraries were removed in this process, so the increase is less steep than it could have been.
Shorter doesn’t mean more readable, so line count isn’t a very useful statistic, but given a similar coding style between Java and Kotlin the comparison is reasonably meaningful. We managed a 20% decrease, a noticeable (if slightly underwhelming) improvement.
Unfortunately I have no pretty diagrams for stability, but our crash analytics suggest the switch from Java to Kotlin produced no new crashes. Using the previous version of the app side-by-side with the current one also reveals no differences. So let’s cross this last fear off the list too.
Unfounded fears aside, some issues were still uncovered along the way. Nothing too serious, but worth mentioning for the sake of transparency.
Annotation processor support was a highly requested feature for Android developers and JetBrains delivered it well before the final release. For most libraries, like Dagger, replacing “apt” with “kapt” in your dependencies is all it takes. However, some libraries, like Realm, pull down other transitive dependencies with annotation processors, which makes things a bit trickier.
In the end, all it required was contacting support and explicitly defining some of those dependencies with “kapt”, but figuring that out took longer than I would have liked.
Credit to the Realm team for being impressively responsive with any Kotlin-related problems.
This one is an optimisation rather than a problem, because if you really need reflection in your Kotlin code, just import the kotlin-reflect package and reflect away. But be mindful that it adds 19,397 methods and 1.7 MB to your app… Yes, really.
How to avoid it? Just keep your reflection in Java.
For example, if you use Realm, which uses reflection, write your model classes in Java. You can still use them in your Kotlin code, but that way all the reflection will be done in Java, meaning that you no longer need the kotlin-reflect behemoth.
Slow builds have plagued Android for years, especially since migrating to Gradle. Things got a little better in Android Studio 2.0 when Instant Run was introduced, but the problem is that Kotlin doesn’t support it yet.
Incremental compilation and Instant Run were promised in the Android Roadmap: the former has since been delivered in 1.0.2, but there’s no timeline for the latter yet. In the meantime, be prepared to drink a lot of coffee while your builds are taking a couple of minutes to finish.
If you’re desperate there is a workaround — JRebel for Android — which supports Kotlin and promises to speed up your builds, but it’s not cheap.
The focus of Kotlin 1.0 was finalising the overall design of the language and implementing the main features. Now that it’s done, the development effort is split between new features for 1.1 and minor non-breaking improvements for 1.0.x.
Aside from all the performance improvements, like the incremental builds, Instant Run and Java 8/9 support, JetBrains have announced some exciting features that exist in other languages and would be great to have in Kotlin.
- Couroutines — more on those here
- Type aliases — short aliases for function types or generic types with long signatures
- Bound method references — method references on specific object instances
Kotlin already has plenty of nice features, but looking at the future ones on the roadmap makes me feel like a kid in a candy store.
So there you have it: a case study of a production Android app successfully converted to Kotlin. Although there were a couple of issues, they were all ones that I can either live with or that will be fixed in the coming months. Either way, the benefits of elegant and readable code without performance tradeoffs make the whole exercise worthwhile.