tl;dr by lazy is very convenient but is underestimated and needs to be understood; oftentimes, lateinit vars are a valid if not superior alternative. Think about your use case before picking which one to go with!
The first pattern we’re looking at is the use of by lazy
for properties. For example, in an activity:
class MyActivity : AppCompatActivity() {
private val displayMetrics by lazy { resources.displayMetrics!! }
// @Px
private val screenWidthPx by lazy { displayMetrics.widthPixels }
// @Px
private val itemWidth by lazy {
displayMetrics.densityDpi.toDouble() * ITEM_SIZE_FACTOR
}
// @Px
private val minRadiusPx by lazy { (screenWidthPx * MIN_RADIUS_FACTOR) / 2 }
// @ColorInt
private val itemTint by lazy { resources.getColor(R.color.my_color) }
// ...
}
As you can see, all properties in this activity have a Lazy
delegate. The displayMetrics
is probably lazy because resources
don’t exist before the activity’s Context
is created — that is, before super.onCreate()
is executed. All the other properties depend on resources
and thus have to be lazy, otherwise their initialisation would cause displayMetrics
' lazy initialiser to be invoked before onCreate()
, crashing the app. We don’t control the activity lifecycle, and that is where the complexity arises.
Since these properties cannot be initialised in the constructor (or at object initialisation time) but only in or after onCreate()
, they all need to take on one of these forms:
- Delegated
by lazy
val
properties (as in the example) - Mutable and nullable
var
s, initialised withnull
, and set to the actual value later on (e.g., inonCreate()
) - Mutable lateinit vars, non-nullable and initialised later on (e.g., in
onCreate()
) - A property with a custom getter — that is,
val
s with an explicitget()
block
Not all those solutions are created equal and all have pros and cons. We’ll only consider by lazy
and lateinit var
s in this article, for the sake of brevity and because they’re the most practical and idiomatic variants.
Delegated properties and by lazy
When you define a property by lazy
, what you’re doing is you’re creating a delegated property using Lazy
, one of Kotlin’s built-in delegates. What does Lazy
do?
In its default form, Lazy
takes in a lambda, which will use to synchronously initialise the property the first time it is accessed. It’s very important to stress that synchronously since, by default, Lazy
invokes that lambda inside of a synchronized
block, which makes it thread-safe, but also rather expensive to invoke. It also caches the resulting value of the initialiser block, so that the next invocations will short-circuit to returning that instead; lastly, it will null
out the reference to the initialiser since it won’t be needed anymore.
If you’re absolutely certain that the property initializer will always only be invoked on a single thread, you can tell Lazy
that you don’t want any thread safety, by explicitly passing a mode to it:
val displayMetrics by lazy(mode = LazyThreadSafetyMode.NONE) {
resources.displayMetrics
}
It’s verbose and a bit ugly, but it works and it saves you from an extra synchronized block you don’t need. You can de-uglify things a bit with a function:
fun <T> lazyUnsafe(initializer: () -> T): Lazy<T> =
lazy(mode = LazyThreadSafetyMode.NONE, initializer = initializer)
// Usage:
private val myField by lazyUnsafe { someExpensiveInitialization() }
Also worth noting is that while we’re looking at val
s, delegated properties are not immutable. Remember, val
does not imply anything with regards to mutability; it merely says that the variable/property is read-only. It is the same as saying final
in Java, but Kotlin’s flexibility means it’s even less stringent. You don’t even need to look at delegates to find examples of non-immutable val
s:
private val aBeautifulNumber: Double
get() = Math.random()
There’s more to Lazy
than this, but it is a good starting point and should cover most of the use cases we run into as Android developers.
Late-initialised properties
An alternative to by lazy
delegation is to use late initialisation, with lateinit var
s. Late-initialised properties are less fancy than delegated lazy properties; they are “regular” var
s which the compiler knows don’t need to, or cannot, be initialised at constructor invocation time.
In this case, you would move the initialisation to onCreate()
:
class MyActivity : AppCompatActivity() {
private lateinit var displayMetrics: DisplayMetrics
@Px
private var screenWidthPx: Int = 0
@Px
private var itemWidth: Double = 0.0
@Px
private var minRadiusPx: Double = 0.0
@ColorInt
private var itemTint: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
displayMetrics = resources.displayMetrics
displayMetrics.apply {
screenWidthPx = widthPixels
itemWidth = densityDpi.toDouble() * ITEM_SIZE_FACTOR
minRadiusPx = (screenWidthPx * MIN_RADIUS_FACTOR) / 2
}
itemTint = resources.getColor(R.color.my_color)
}
// ...
}
As you will notice, we cannot have lateinit
properties of “primitive” types — I put quotes around that because there’s no primitive types in Kotlin. In those cases you need to either have those properties nullable and initialise them to null
, or set a temporary default value in their initialiser and then set it to the correct value as soon as possible.
Late-initialised properties incur no runtime performance cost, as opposed to delegated properties, but force you to use var
s and as we’ve seen they can’t be used with “primitive” types. They also can’t be used with nullable variables, but that would make little sense anyway.
Late-initialised properties force you to explicitly initialise them before you can access them, as opposed to by lazy
. I think that’s actually a good thing for Android developers, since you know when they are available. You can also check if they are initialised, starting in Kotlin 1.2, with the isInitialized
extension function on the property reference.
If you use dependency injection frameworks like Dagger 2, lateinit
is your best friend, and the only reasonable way to obtain injected dependencies that can’t be injected via the constructor, as well.
My two cents
You shouldn’t use by lazy
for every single property. You should consider it to be the exception and not the norm, for several reasons. Let’s take the example of using by lazy
for fields looking up a resource value.
Semantically, because it’s meant to offset the creation of heavy objects (e.g., a DB connection), especially for those you are not sure if and when they’ll be used. The lookup is not very heavy in this case, and usually we know we’ll need the looked up value.
From a semantics standpoint, here lateinit
is the correct thing to use in cases like this, as it signifies “I will need this, I just can’t assign a value at init { }
time, but I will ASAP”. If late initialisation is what you’re looking for, use late-initialised properties.
In a terrible and completely made up example, assume you have a class which does a bunch of things, and also happens to have one API method which requires DB interaction — say, for the sake of this example, a save()
method (please please please don’t ever do it). You don’t want to create the DB connection when you create the model instance, because you don’t know if and when you’ll need to persist the data. Instead, you want to have as fast an initialisation as possible, so it’s cheap to go through those data models; this is particularly important when dealing with immutable objects which require extensive copying for their manipulation. In this case, by lazy
might be the right place to store the DB connection; it is very much a decision that you should be taking on a case-by-case basis.
From the perspective of performance, by lazy
imposes a small overhead, and makes execution times unpredictable for the accessor. It doesn’t look like it does, and it’s very easy to get carried away and use it to model late initialisation. Surely the overhead can be negligible in some cases, like when compared to creating a DB connection, but it can be a very significative overhead when you’re wrapping a (relatively) cheap operation such as obtaining a system service or a resource value.
In the latter case, all those initialisers are in and by themselves inexpensive; GC cost would come into consideration in some other cases, but it’s not a concern here. These initialisers aren’t doing anything expensive. Using by lazy
for those is a(n extremely minor) unnecessary perf hit. Now, the performance hit is not really what concerns me, although knowing it’s there is an annoyance, like unnecessarily boxing and unboxing primitive types in Java.
Lastly, using by lazy
can be dangerous. Not in this case, but in other cases it can be. It’s potentially dangerous because you don’t know necessarily what code will first access the value, nor when, nor how the code will change down the line and if this will be kept into consideration. You’re also lulled into a safe confidence of "well it’ll be initialised for me” and risk running a long series of lazy initialisers at the same time if the property you’re accessing depends on further lazy properties, chaining a very expensive series of heavy initialisers (plus their synchronisation cost) behind what looks like a normal, cheap, property accessor. In that sense, and when used to emulate late-initialised properties, by lazy
masks a lateinit
-esque kind of mutability behind a val
which is not really immutable, since it has a meta-state in which its value is not initialised yet. While a by lazy
property looks like a val
, it definitely doesn’t quack like one, and that can be very misleading.
Using by lazy
and relying on implicit initialisation requires careful design and consideration, which is not always the case. It’s like having open
classes on an API; most people don’t think about it and live happily with it, until something somewhere breaks and then they find out months or years of code built on top of implicit behaviour which make bug fixes a monumental task. The same goes for lateinit
of course, but the latter forces you to think about those things; it’s a mutable variable, so you immediately have that feedback, and you need to have an explicit initializer in the code, which will guarantee when it is initialised.
In light of these points, lateinit
is my default approach; I’d use by lazy
in those rare cases, but it is quite uncommon in my experience — the only scenarios in which by lazy
might be necessary because of lateinit
shortcomings would be when getting things which may be null
, such as optional system services. Even then, I may opt for a nullable var
instead, depending on the cases.
By using lateinit
you will lose the false perceived immutability of a val
, and you’ll have to type a few characters more, but you’ll gain the understanding that that property is actually not immutable nor really “final”, and in being forced to be explicit in its initialisation, gaining in readability and maintainability. In my eyes it’s a worthy compromise.
class MyActivity : AppCompatActivity() {
private lateinit var presenter: MyPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
presenter = MyPresenter(
displayMetrics = resources.displayMetrics,
resources = resources,
navigator = MyNavigator(this)
)
}
override fun onStart() {
super.onStart()
presenter.startPresenting()
}
override fun onStop() {
super.onStop()
presenter.stopPresenting()
}
// ...
}
Ask yourself: if you use by lazy
, what guarantees the initialisation block won’t be run before its contents are ready? Or that it won’t be executed in a hot path, maybe after some refactoring when people forgot the cost of doing so, or simply forgot to check when it’s initialised?
Want to read more?
Take a look at the other articles in this series.