Safely accessing lateinit variables
Kotlin, by design, doesn't allow a non-null variable to be left uninitialised during its declaration.
To get past this issue, Kotlin's lateinit
allows us to declare a variable first and then initialise it some point in the future during our program's execution cycle.
The concept is simple, but when we try to access an uninitialised property, it's a different story. Let's talk more about that.
First, let's crash our app
Whenever we declare a lateinit var
, we need to initialise it before we can access it. Failing to do that will result in an exception as follows:
Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property fullName has not been initialized at UninitializedPropertyKt.main(UninitializedProperty.kt:3)
The above is not one of those warnings that we can safely ignore and move on. An exception like this will crash our app.
And this is a common situation. For example, in an Android app:
We might have a list of news items that we need to fetch from a remote server. When the data finally arrives, we need to initialise a RecyclerView
adapter, let's say NewsAdapter
based on that data to show up the news articles.
What do you think will happen when we try to access that adapter before we initialise it with the fetched data?
Our app crashes because of the exception we talked about earlier. The lateinit var adapter: NewsAdapter
has not been initialised.
Does that mean lateinit
properties are useless? No.
Here's a secret:
Before accessing the property, we can check if it's initialised or not.
There's a reflection based API
On Kotlin 1.2 and up, we can quickly check whether a lateinit
property has been initialised or not using a reflection based API.
Here's how it works:
lateinit var fullName: String
if (::fullName.isInitialized) {
print("Hi, $fullName")
}
You can read more about this API in the official blog post announcing this update.
The backing field might not be accessible always
There's a small caveat here. This reflection API works only on properties which are defined:
- In the same class where the check is being made
- In an outer class
- As top-level in the same file
So, if we have a lateinit
property in one class and try to check whether it's initialised or not in another class, like this:
class LateComer {
lateinit var student: Student
}
class School {
val lateComer = LateComer()
fun printLateComer() {
if (lateComer::student.isInitialized) {
println("Late Comer: ${lateComer.student}")
}
}
}
The program will get a compile time error saying:
Error:(9, 32) Kotlin: Backing field of 'var student: Student /* = String */' is not accessible at this point
In some case, where we need to verify whether lateinit
property of some class is initialised or not, there's a workaround.
Taking note from this StackOverflow answer, we can check lateinit
properties of other classes by adding a new method on the target class:
class LateComer {
lateinit var student: Student
fun isStudentInitialised() = ::student.isInitialized
}
It is not an elegant solution but it works.
Safe to use
This API is neither experimental nor unstable. I have been using it for a long time, and it works every time.
Feel free to take advantage of this small feature and instead of ditching lateinit
altogether and falling back to nullable properties.