Last week I wrote about testing RxJava Observables. This time, I recently discovered some neat tricks with Kotlin fields that I'd like to share.
The project I am currently working on is switching from using manual constructor injection to using Dagger. Along with this, we are converting many files from Java to Kotlin.
I don't know about you, but one of my favorite features of Kotlin is the inherent null-safety of the language. That is, if you want to have a variable be nullable, it is an explicit part of that variable's type (represented by the
? character - for example:
var foo: String? And, even better yet, if you try to use a nullable variable without checking to see if it is null, the code won't compile. You also cannot assign nullable values to non-null values without ensuring the value is non-null (or pass a nullable value into a method expecting a non-null value, etc.)
There's one tricky problem with this approach - and that is when a variable is not initialized right away. This comes right back to the inherent null safety of the language - in Java, you can declare a variable, but not actually give it a value, and simply assign it the desired value later on during the course of initialization. With Kotlin, unless you want that value to be nullable, you can't do this as easily, as you have to supply a value when you declare it.
… or do you?
It turns out Kotlin has a few tricks up its sleeve to address just this problem.
One of these is the
lazy keyword: https://kotlinlang.org/docs/reference/delegated-properties.html
This makes use of the delegated property support in Kotlin. Lazy is a built-in function that supports lazy initialization of values - their value isn't computed until the first time someone tries to access them.
However, this wasn't quite what I was looking for in this case, as we want our values to be injected via Dagger.
Thankfully, Kotlin also has the
lateinit keyword: https://kotlinlang.org/docs/reference/properties.html
This looks exactly like what we want! It basically is an explicit way of telling the compiler "I know this doesn't have a value yet it's a non-null type. Trust me - it will be there before anyone tries to use it." And, better yet, if due to programmer error it isn't actually initialized by the time someone tries to use it, Kotlin throws a special type of exception that says "You forgot to initialize this variable" - which is very different than the much more common NPE (Null Pointer Exception), so it makes it easier to identify what has gone wrong.
You may have noticed that Kotlin refers to member variables as Properties. In Java, it was typically bad practice to expose member variables as
public - since this breaks encapsulation. But, in Kotlin (and other languages, such as C#), it's much more broadly acceptable to do this as the language allows you to reference these like variables (setting/getting values) - but under the hood they're really still set/get functions.
This becomes interesting when calling Kotlin code from Java. Where you might have written a property in your Kotlin class:
var foo: String = "bar", in Java, by default, you would still have to call
However, Kotlin has a special annotation -
@JvmField, which when applied to a property, allows Java to reference this like a regular variable.
This is all great, except if you try to use
@JvmField on a
lateinit property, you'll find that you can't. This stumped us for a while, until we read the documentation a little more closely and discovered why - because
lateinit properties are automatically exposed as fields: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#instance-fields
Now we're happily on our way, cleaning up our dependency injection with Dagger, and able to use
lateinit properties so we can avoid lots of extra null checks, but also still access these as fields from Java.
What other cool properties of Kotlin fields have you used before? Let me know in the comments below! And, please follow me on Medium if you're interested in being notified of future tidbits.
This tidbit was discovered on May 14, 2020.