Odd things to look out for when converting code to Kotlin

Gabor Varadi
AndroidPub
Published in
8 min readOct 14, 2017

EDIT from the future: wanna have additional jumpstart in Kotlin? Then check out my GUIDE TO KOTLIN! Yes, it’s a Github Wiki, and it is free. Go forth and learn Kotlin. After reading this article, of course.

Kotlin is the bees knees, but just throwing it at your project can easily make you run into unexpected errors. Here’s a few interesting tidbits and errors that I found while converting a larger existing codebase to Kotlin (using the built-in Java to Kotlin converter), which I did primarily for the sake of research.

In no particular order, although there’s some juicy stuff below:

1.) const val is not the same as val

companion object {
val ENGLISH = "en"

If you want to use this as a parameter inside an annotation (like @Named(NAME)), you can’t.

You need to also specify const, like so:

companion object {
const val ENGLISH = "en"

— — — — — — — — — —

2.) Field names that start with $ are escaped with backticks (`)

val `$ID` = NAME + "." + ID

Also, the converter doesn’t seem to do string interpolation for you automatically, so you can change this to be

val `$ID` = "$NAME.$ID"

Also, if your code contains fields like this, then Alt+F7 (Find Usages) doesn’t pick up on it. And it’s also used with backticks elsewhere.

val where = StringBuilder("${EventTable.`$ID`} IN (eventIds))")

So if you intend to make a codebase for Kotlin, you probably shouldn’t start variable names with $.

— — — — — — — — — —

3.) method name in is also escaped with backticks

Because it’s a keyword: for(x in list) { … }

— — — — — — — — — —

4.) Interfaces defined in Kotlin cannot be used as lambdas

To use the lambda syntax of Kotlin with classes in Kotlin, one must use the built-in Kotlin functional interfaces (like Boolean -> Unit and so on).

SAM-conversions only work for Java methods, not supported for Kotlin functions, because Kotlin has nice functional types, and there’s no need for SAM conversion.

This feature works only for Java interop; since Kotlin has proper function types, automatic conversion of functions into implementations of Kotlin interfaces is unnecessary and therefore unsupported.

For interfaces defined in Kotlin, you must explicitly create an anonymous subclass using object: MyInterface.

It’s not that easy with Kotlin after initial conversion if the interface is in Kotlin — change to Kotlin function types, or use inline objects (anonymous implementations). Back to where we started, with Java before lambdas, huh? 😜

Apparently, SAM and lambdas work well with existing Java code…

In fact, it can solve the following issue:

By doing:

— — — — — — — — —

But for Kotlin interfaces, we need to create inline objects.

The other alternative is to use the name not as an interface, but as a typealias instead for the given Kotlin functional type (in this case, (T) -> Boolean).

With that, we’ve actually become able to use lambdas in Kotlin again — but we had to explicitly handle Kotlin’s functional types as well.

— — — — — — — — — —

5.)=== (Kotlin) is == (Java), and == is equals()

And also < and > are mapped to compareTo()

— — — — — — — — — —

6.) There is no ternary operator like condition ? true : false

The solution is to use if(condition) true else false.

Java
Kotlin

But we can also use the when keyword.

Kotlin with `when`

— — — — — — — — — —

7.) smart-cast does not like mutable field variables

The solution is that the field must be saved into a val which means it can no longer change… and if it still claims to be nullable even though you know it’s not, you can yell at it with !!.

(It’s best to keep volume to minimum, though — and use ?. or ?: where applicable)

— — — — — — — — — —

8.) “this property must be initialized or made abstract”

This actually just means this variable is initialized too late (not in constructor), but it is specified as non-null, so we must change it to be lateinit.

Typically this is also needed for views bound with ButterKnife, instead of making them nullable.

— — — — — — — — — —

9.) “Cannot access variable, it is private”

Using backing fields and custom accessors, we can fix this.

You can access the value set by set as value, and the field as field.

— — — — — — — — — —

10.) Accessing the external class instance reference from nested class changed from ExternalClass.this to this@ExternalClass

— — — — — — — — — —

11.) accidental override of existing getters/setters

You used to have fields that could be called anything… That’s not necessarily the case in Kotlin.

If this happens, just refactor/rename the variable.

— — — — — — — — — —

12.) you CAN define multiple constructors

If you used the class’s constructor, you’d need to use @JvmOverloads for Android’s reflection-based layout inflation to work.

Although then you’d need to execute the custom init(context)a method as init {...}. This is trickier, so I’d just keep the constructors!

— — — — — — — — — —

13.) Parcelable’s CREATOR must be marked with @JvmField

If it is not marked with @JvmField, then restoring the class will no longer work. There was no lint for this.

@JvmField is also needed for Mockito @Rules in tests.

— — — — — — — — — —

14.) Defining methods named get__ in an interface is done with val

I actually think the term override val looks somewhat interesting.

— — — — — — — — — —

15.) add() is not available on List

This is because a List<*> in Kotlin is immutable by default, so we must use mutableListOf() manually first, then use toList() when we are done with building the list.

— — — — — — — — — —

16.) ButterKnife/Dagger/Realm no longer generates things

You need to change all annotationProcessor scoped dependencies to use kapt, and you need to also call apply plugin: ‘kotlin-kapt'.

If you use Realm, then you should apply kotlin-kapt before applying the realm-android plugin.

For ButterKnife, it’s also worth noting that the views shouldn’t be made nullable (unless they were indeed nullable), if they are meant to be found, then they should be lateinit.

— — — — — — — — — —

17.) You might need to explicitly specify the annotation target sometimes

I didn’t actually run into this this time, but it might be worth noting.

— — — — — — — — — —

18.) Platform declaration clash

Well… we didn’t use the fact that this class extended java.util.Collection<T> anyways… so removing the methods and the interface works! :D

Honestly, this was a bit confusing to figure out otherwise at first glance.

— — — — — — — — — —

+1.) Kotlin DSL magic: builders can be replaced with apply()

Code like

Can be replaced with

Which is super-interesting!

You can also chain lambdas directly using infix functions.

I saw this in this video and I just had to add it! Although I’ve seen libraries abuse the hell out of infix functions, so it’s probably best to keep it readable and reasonable.

— — — — — — — — — —

Conclusion

Hopefully this article helped a bit to see what kind of things might come up when you convert a project from Java to Kotlin, or if you’re just learning.

Considering how new the language is, we’re all just learning 😄

--

--

Gabor Varadi
AndroidPub

Android dev. Zhuinden, or EpicPandaForce @ SO. Extension function fan #Kotlin, dislikes multiple Activities/Fragment backstack.