Java to kotlin

Think Again Before Migrating from Java to Kotlin

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 of Kotlin you should be aware of before migrating your code base as well.

You can compare Kotlin advantage and disadvantage and then you can find you have to migrate from Java to Kotlin or not.

Image result for drawback of kotlin

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.

YOu must see : 5 cool things you probably don’t know about Kotlin

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.:

class User {
 int id;
 String name;
 @Status
 int status;
  
 @IntDef({Status.MEMBER, Status.ADMIN})
 public @interface Status {
 int MEMBER = 0;
 int ADMIN = 1;
 }
 }

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 :

class User {
 var id: Int = 0
 var name: String? = null
 @Status
 var status: Int = 0
  
 @IntDef(STATUS_MEMBER.toLong(), STATUS_ADMIN.toLong())
 annotation class Status
  
 companion object {
 const val STATUS_MEMBER = 0
 const val STATUS_ADMIN = 1
 }
 }

Nevertheless, if you were to try this code by yourself, you’d see that Lintcomplains 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 @JvmFieldbut doing so will prevent Kotlin to create the getter and setter. You will have to do it yourselves (if needed) like this :

class User {
 var id: Int = 0
 var name: String? = null
 @Status
 @JvmField
 protected var status: Int = 0
 @Status
 fun getStatus() = status
 fun setStatus(@Status status: Int) {
 this.status = status
 }
  
 @IntDef(STATUS_MEMBER.toLong(), STATUS_ADMIN.toLong())
 annotation class Status
  
 companion object {
 const val STATUS_MEMBER = 0
 const val STATUS_ADMIN = 1
 }
 }

Edit : A cleaner solution consist of only annotating the getter and setter as follow :

class User {
 var id: Int = 0
 var name: String? = null
 var status: Int = 0
 @Status
 get() = field
 set(@Status value) {
 field = value
 }
  
 @IntDef(STATUS_MEMBER.toLong(), STATUS_ADMIN.toLong())
 annotation class Status
  
 companion object {
 const val STATUS_MEMBER = 0
 const val STATUS_ADMIN = 1
 }
 }

Edit : It is possible to annotate the getter and setter without re-declaring their body, which is of course better :

class User {
 var id: Int = 0
 var name: String? = null
 @setparam:Status
 var status: Int = 0
 @Status get
  
 @IntDef(STATUS_MEMBER.toLong(), STATUS_ADMIN.toLong())
 annotation class Status
  
 companion object {
 const val STATUS_MEMBER = 0
 const val STATUS_ADMIN = 1
 }
 }

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 🙂