The drawbacks of migrating to Kotlin

Probably you, right now, after reading the title

Kotlin is awesome and we, at Frontback, have been fiddling with it for quite some time. And when Google officially announced first class citizen support for it, we decided to migrate our Android code as well. Nevertheless, there are some drawbacks you should be aware of before migrating your code base as well.

Git history

Migrating a class from Java to Kotlin means losing easy access to your code’s history. As you probably know, if you had MyActivity.java and you migrate it to MyActivity.kt git will most of the time consider MyActivity.kt to be a new file and MyActivity.java to have been deleted. Of course, you can still browse your history and find MyActivity.java back but it’s cumbersome. Nevertheless, there are techniques to mitigate that problem somehow but it will still remain a problem.

Introducing bugs/crashes

That’s the biggest one for me. There are multiple reasons why you might introduce bugs in your code such as lack of experience in Kotlin, trusting the converter too quickly and the interoperability with Java. I want to bring your attention to the latter. There are two ways to introduce crashes related to the interoperability with java :

  • Calling a Kotlin methods from Java
  • Extending a Java classes in Kotlin

Calling a Kotlin methods from Java

It’s no news for you that Kotlin makes a clear distinction between nullable and non-nullable types and that Java doesn’t. It’s also pretty obvious that since Java doesn’t, you can pass null (either directly or indirectly via a variable) to a Kotlin method that wants to receive a non-nullable type from Java. If that’s the case, Kotlin will throw an exception pointing out which variable received was null.

From a completely idiomatic point of view, it’s the intended behavior, Kotlin does its best to tell you what’s wrong and you did pass an invalid argument. You might think, if I’m passing a variable to a method that I though deserved a non-nullable type, I would probably have not checked any calls on that variable and Java would have crashed with a NullPointerException anyway. That might be true in some cases but we also often pass variable from method to method and maybe you would have checked down the line but Kotlin will crash as soon as it receives the variable. To mitigate this problem, check your code and only make parameters non-nullable if you are entirely sure otherwise, go with the nullable type.

Extending a Java classes in Kotlin

When we started working with Kotlin, we thought it handled everything coming from Java as nullable but that is not the case. Kotlin has a special type called platform type that can be either nullable or non-nullable. It is represented by an “!” after the type, such as String! (PS: you cannot declare such a type, obviously). That is why, when you infer the return type of a method based on a Java method, Android Studio shows a little warning and ask you to declare the return type explicitly. It’s also the reason why when you override a Java method in Kotlin, you can either decide to receive a nullable or non-nullable type. Be careful if you decide the parameter is non-nullable, Kotlin will assert this and will throw an IllegalArgumentException at runtime if the received parameter is null. The same advice as for “Calling a Kotlin methods from Java” applies.

It is also good to know that if a Java method has been annotated with @Nullableor @NonNull , Kotlin will consider this as the source of truth and will allow only the corresponding type as parameter.

IntDef & StringDef annotations

One way of declaringIntDef and StringDef that I liked was to declare the @interface class inside the related class and define the constants withing that interface. E.g.:

In Kotlin, an annotation class cannot have a body anymore. So you either have to declare your IntDef in Java in a class of its own or declare the constants in the companion object like so :

Nevertheless, if you were to try this code by yourself, you’d see that Lint complains at line 4, stating “This annotation doesn’t apply for type void”.

Edit : This problem only occurs for @IntDef not for @StringDef .

One solution to that problem is to annotate the status field with @JvmField but doing so will prevent Kotlin to create the getter and setter. You will have to do it yourselves (if needed) like this :

Edit : as noted by Christophe Beyls, a cleaner solution consist of only annotating the getter and setter as follow :
Edit : as noted by Eugen Pechanec, it is possible to annotate the getter and setter without re-declaring their body, which is of course better :

Also note that annotating the field only (only possible with @StringDef ) is not sufficient. You need to annotate the getter and setter (or your custom methods) to make it work.

Finally, using @get:Status doesn’t seem to work so overriding the getter seem to be the solution here.

Conclusion

Kotlin is great but it’s not perfect and it will take some time for us to get used to all the little quirks and gotchas. Still, JetBrains and Google are actively behind Kotlin so be sure it will only get better :)