1. Overview
In Kotlin, there are a few different approaches to lazy-initialize or even late-initialize variables and properties.
In this quick tutorial, we’re going to learn to check the initialization status of a lateinit variable.
2. Late Initialization
Using lateinit variables, we can defer a variable’s initialization. This is especially useful while working with dependency injection or testing frameworks.
Despite being useful, there is one big caveat while using them: if we access an uninitialized lateinit variable, Kotlin throws an exception:
private lateinit var answer: String
@Test(expected = UninitializedPropertyAccessException::class)
fun givenLateInit_WhenNotInitialized_ShouldThrowAnException() {
answer.length
}
The UninitializedPropertyAccessException exception signals the fact that the underlying variable is not initialized yet.
In order to check the initialization status, we can use the isInitialized method on the property reference:
assertFalse { this::answer.isInitialized }
Here, with the :: syntax, we get a reference to the property. This obviously returns true when we assign something to the variable:
answer = "42"
assertTrue { this::answer.isInitialized }
Please note that this feature is only available on Kotlin 1.2 or newer versions.
3. isInitialized Access
In this section, we’ll learn about a widely encountered access issue for the isInitialized method. Further, we’ll implement a solution for the issue.
3.1. Understanding the Issue
Let’s start by defining the Class1, Class2, and Class3 classes, such that Class2* contains a lateinit member for Class1, and Class3 contains a lateinit member for *Class2:
class Class1
class Class2 {
lateinit var class1: Class1
}
class Class3 {
lateinit var class2: Class2
}
Now, let’s try to write the isInitializedDirectAccess() function within Class3 to check the initialization status for the class2 member:
fun isInitializedDirectAccess(): Boolean {
return ::class2.isInitialized && class2::class1.isInitialized
}
It’s important to note that we’re using the isInitialized method on the class2 and class1 members directly.
Unfortunately, when we compile our code, it gives a compilation error for such access:
Backing field of 'var class1: Class1' is not accessible at this point
We encounter this error because the isInitialized method is only accessible for properties declared within the same type, an outer type, or at the top level in the same file. On the other hand, the class1 member is accessible only within Class2 and not directly within Class3.
3.2. Fixing the Access Issue
To fix the access issue, we can expose the isInitialized method for class1 member of Class2 through the isInitialized() function in Class2:
fun isInitialized(): Boolean {
return ::class1.isInitialized
}
Further, we can use write another isInitialized() function within Class3 that calls isInitialized() function of class2:
fun isInitialized(): Boolean {
return ::class2.isInitialized && class2.isInitialized()
}
Now, let’s verify that our approach is working fine by getting the initialization status using the isInitialized() function:
val class1 = Class1()
val class2 = Class2()
val class3 = Class3()
class2.class1 = class1
class3.class2 = class2
assertTrue(class2.isInitialized())
assertTrue(class3.isInitialized())
Perfect! It works fine when the members are initialized.
Lastly, let’s also check that it’s returning false when the members aren’t initialized:
val class1 = Class1()
val class2 = Class2()
val class3 = Class3()
assertFalse(class2.isInitialized())
assertFalse(class3.isInitialized())
As expected, the isInitialized() function returns false in this case.
4. Conclusion
In this article, we saw how to check the initialization status of a lateinit variable. Additionally, we learned about the isInitialized method for checking the initialization status.
As usual, the sample codes are available over on GitHub.