A practical explanation for initialization modifiers in Kotlin
Recently I hold a Kotlin Workshop for a client. The workshop central idea lies in spending a couple of hours showing the language itself, going individually through some of its capabilities and on the second half developing a full app able to connect to an API, fetch data, storing it and displaying it in a RecyclerView. I found this Workshop concept to be apt for an audience novel to Kotlin or that is currently starting. Most of the initial concepts are explored or touched, and you have an app with an initial architecture and approach that can later on be extended and reused in other professional projects.
Yet at this Workshop it happened something that I experienced anteriorly, and it was encountering a gap amidst a theoretical concept and a practical application. This time, it happened with the Initialization Properties for Kotlin.
(to make this occurrence over-interesting, the frequency increases when you are jumping from a language like Java into a language like Kotlin. Over many years you have developed a routine lacking so many benefits that your brain it’s found in a constant state of clutter).
As a daily developer and an occasional trainer, I encounter this gap with a certain periodicity. It works like this:
- There is a theoretical concept that needs to be explained.
- I dust off the theory. I explain its peculiarities. I go through the manual description and its correspondent attributes.
- I make sure the audience understands the theoretical concept. I ask them questions, I look for answers. Everything looks fine.
- Then I get the question: “yes, everything is fine, but when do we use this?”
To truly understand a concept, you need to be able to walk and moonwalk over the theoretical domain and the practical applications. It is not enough to understand HOW something works, to know its inner rules and learn the manual or tutorial by heart. You need to come across the point where you can think of a practical implication where it could be needed. That is exactly what engineering and practical sciences are: putting the theoretical knowledge into the practical work.
At the aforementioned workshop I got inquired about this problematic: “yes, we understand how the initialization properties work. But can you tell us a practical case where to apply this?”
This article aims to serve as a response to my questioner.
Initialization Properties in Kotlin
Let’s start first with all the Initialization Properties available for us in Kotlin. There is no equivalent mechanism in Java, so if you are coming from this realm you might find it first newfangled.
An initialization property determines, unsurprisingly, how a variable will be initialized. In Java a variable is initialized when the constructor is called. Easy. And limiting. Kotlin offers us a few more options:
A lazy property is initialized when it is accessed for the first time. Look at the following example:
Unless you access the aVar, its value will never be set. This is the two lines explanation, although a lazy property has more sugar. The value is computed only in one thread, and all threads will see the same value. Also, the first time you access the Lazy property the initialization takes place. The second time, this value is remembered and returned. For example, take a look at:
This code will actually print the following:
I am computing this valueHolaHola
As mentioned in the introduction, the theoretical aspect is easy to grasp. We will return later to the lazy modifier.
The modifier lateinit allows us to delay the initialization of a variable. Let’s consider the following piece of code:
If you try the above code section in your compiler, there will be no error thrown. Try to do it without lateinit, and you will end up with a compilation message.
By writing lateinit we must later on (and before it is accessed) take care of initializing the variable. If we do not do it, an exception will be thrown:
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property myVar has not been initialized
Ah! And some more details at the top: lateinit does not work with primitive types. You need to use delegates.
The practical territory for initialization modifiers
We have grasped the theoretical meaning and taken a first well sip of its implications. In order to keep mastering our command of this modifiers, we need to find use cases where we can apply them. Let’s check some of them.
If you use Dagger or similar Dependency Injection Frameworks, you might be used to code snippets similar to the following one:
The variable is actually not initialized, but afterward injected. By using lateinit we allow the initialization to happen in a posterior instant.
High-density computation or big objects
Think of an object that requires an intense computation to be created, or that they take a lot of space in memory. A Bitmap is always a good example. You instantiate the object, the object is already in memory and then you need to wait until you use it. This is an effortless example of a variable that should rather be declared as a lazy object. Keeping this simple action in mind will optimise your code from the initial instant. The same applies for costly operations, such as opening files from the disk system, etc.
(Juhani Lehtimäki actually pointed me out in one conversation this was a great use case)
You need to access a resource (in Android, this might be drawables, string, dimens…). Unless you are in that state (for instance, an error happening in a particular condition) you do not need to load them. Bingo, this is again a suitable scenario for the late initialization.
A further consideration: by lazy is restricted only to
val.lateinit is only allowed on mutable properties
As a summary, try to think of a variable with its instantiation depending on a particular state. If you can think of an example, you have already a candidate for lazy or late initialization.
I write my thoughts about Software Engineering and life in general in my Twitter account. If you have liked this article or it did help you, feel free to share it, ♥ it and/or leave a comment. This is the currency that fuels amateur writers.
Thanks to my colleague and friend Marius Budin for his feedback on this article.