Odd things to look out for when converting code to Kotlin
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
.
But we can also use the when
keyword.
— — — — — — — — — —
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 @Rule
s 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 😄