Kotlin and Android #1 — by lazy

    Kotlin and Android #1  —  by lazy
    Cover image: “Griffon” by Edoardo Brotto — on flickr

    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:

    1. Delegated by lazy val properties (as in the example)
    2. Mutable and nullable vars, initialised with null, and set to the actual value later on (e.g., in onCreate())
    3. Mutable lateinit vars, non-nullable and initialised later on (e.g., in onCreate())
    4. A property with a custom getter — that is, vals with an explicit get() block

    Not all those solutions are created equal and all have pros and cons. We’ll only consider by lazy and lateinit vars 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 vals, 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 vals:

    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 vars. Late-initialised properties are less fancy than delegated lazy properties; they are “regular” vars 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 vars 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.

    Sebastiano Poggi

    Sebastiano Poggi

    “It depends” 🤷‍♂️ — Google Developer Expert for Android, Flutter and Identity. A geek 🤓 who has a serious thing for good design ✨ and for emojis 🤟working at JetBrains (opinions my own)

    London and elsewhere https://sebastiano.dev