Kotlin @ BUX
How an App's development language went from means to an end to a beloved tool.
We need some background to contextualize how Kotlin happened at BUX.
When the company started, 4 years ago, we were 3 Mobile Developers writing the iOS App. As a startup, we needed to get the MVP out of the door ASAP, so focusing in a single platform seemed pretty reasonable.
But at heart, I've always been an Android lover. So much that, whenever time allowed, I'd stop working on the iOS App and keep writing my Android spin-off, without proper User Stories or custom Design (maybe that's the reason that, UI-wise, the Android App looks just like the iOS one).
As I expect pretty much everyone to agree, writing Java (even if I was stuck with Java 6) was much better than having to write Objective-C code. Life was good, even knowing that the IDE collapsed View.OnClickListener implementation was an ugly, full-fat, Anonymous Class instance.
But then, on September of 2014, Swift was announced. The much awaited modern language for iOS development. It was so needed that whoever could was jumping straight into it.
BUX fit in that category. Since the developers, as separate teams, have total freedom to (responsibly) adopt whichever tools they'd like, Swift quickly became the standard language for iOS development. With every new class being written in Swift and Obj-C classes getting converted whenever needed, I was starting to get jealous.
Upon realizing that I was enjoying more my work when contributing to the iOS App because of the language, I became desperate. I started resorting to all kinds of bytecode weaving, like adding retrolambda support, or black magic. All of that to try and ease the pain of knowing that my neighbor's lawn was greener.
The Kotlin advent
It was only on February of 2016 that Kotlin 1.0 was announced. I'm not one to feel compelled to try new technologies or tools just because all the cool kids are doing it, but it was love at first sight.
I've decided to take one of my hackathon days (of which we have one every 2 weeks) to experiment with it, take on the Koans, automatically (suck it, Swift!) convert some Java classes to Kotlin, measure impact in compilation time, method count and APK size, etc.
The result was that, although there was a price to pay, specially for compilation on the early days, it seemed certain that it would pay off.
And pay off it did. Working with Kotlin not only brought back that nice, warm fuzzy feeling that I had not experienced since the first time I worked with Android Studio, but also felt more productive. More and more as the tooling and integration with Android Studio kept improving.
We started writing any new code in Kotlin and converting existing code from Java if it was being touched and what we were modifying would be more complex than an if or so. Kotlin was growing organically in our code base.
When, during the Google I/O 2017, it was announced that Kotlin was being adopted as a first citizen development language for Android, I felt very, very happy. Not the "I told you so" kind of happy (OK, maybe a bit of this one too), but happy for the Android development community.
As the language moves from "JetBrains' internal version of Java" to "officially supported by Google", I believe that more companies and individuals should be experimenting with and getting irreversibly hooked up to it.
I'm bold enough to say that it will become the standard language for Android development over the next few years. And call me conspiracy-theorist, but Android sample codes are being given in Kotlin and Java; noticed the order? Kotlin first:
74.8% of our App is written in Kotlin.
Java has 35000 lines of code and Kotlin 94211.
There are 362 .java and 1349 .kt files.
By consequence, Java files have an average of 97 lines, while Kotlin ones have 70.
By assuming that one Java file represents, on average, more or less the same fraction of the App as a Kotlin one, we could say that, when using Kotlin, we need to write 28% less code. And we are happier while doing it.
Well, no big change comes without its pains.
Instant-Run was undoable in the beginning, but Instant-Run was a hassle by itself, so I can't exactly pinpoint the blame on Kotlin.
I was writing Kotlin as learning it, so Kotlin code was being refactored pretty often.
I'm positive that there were other minor problems back them, but minor enough to have slipped from my mind already.
But some problems were big. Production big:
- Lint checks didn't integrate well with Kotlin. This forced me to write a custom script to be able to find unused localizable Strings in a Java/Kotlin project and, more problematic, it caused us to ship unchecked Lollipop specific code that would cause the App to always crash on KitKat devices.
- Too aggressive non-null code generation. When overriding a method like Activiy.onActivityResult(), the "data: Intent" parameter came by default like that, non-null. If you're not using the "data" parameter in your code (as quite often is the case) this probably would go unnoticed and your App would crash whenever that function would be executed. Yes, this also ended up being shipped.
These were problems, now addressed. And the production f*ckups were noticed and fixed in a matter of hours after initial rollout.
But there is still a "white elephant in the middle of the room" kind of problem for us: Annotation Processing sucks big time with Kotlin.
It wasn't available for Kotlin since the beginning; there were some faulty implementations later on; but now there is a working solution. Yeah, it's complicated.
Although KAPT (Kotlin Annotation Processing) now works like a champ, it is… extremely… slow.
For the BUX App, we avoided using it as long as possible. Dagger 1 classes stayed in Java and we tried to use alternatives for all the other functionalities. It worked for a while.
But when we refactored the App to migrate from Dagger 1 to Dagger 2, it made little sense to keep all the new Component and Module classes in Java, so we adopted KAPT.
Our cold full-compilation time went from ~1min40s to ~4min, a ~240% increase.
By running the gradle build command with the profile option, we could see that the kaptGenerateStubs*Kotlin task was taking~1min15s and kapt*Kotlin another ~30s. So KAPT was the culprit for, if not all, the biggest part of the compilation time increase.
What now, then?
Well, KAPT (still) sucks. But it has been improved throughout the life of Kotlin and I only expect that such a pain point will continue to receive attention from its developers.
We intend to write two new Apps that should ship by the end of the year without any Java code.
After the Kotlin adoption we welcomed 2 Android developers that had none or only non-professional experience with Kotlin. Now, sometimes, I get Kotlin feedback and insights from them. I believe they came to love it too.
But don't worry and rush to adopt Kotlin just out of FOMO, Java isn't going anywhere (you can even use some Java 8 features with Android now), the JVM is required to run Kotlin and they play nicely together.
So, maybe, slowly, just give it a go and see how you like each other.