Delegated properties in Kotlin

Delegated properties in Kotlin

If you are like me, you've probably used by lazy multiple times before wondering how that works. More recently I've been using by viewModels in Android  and I said it's a good time to finally look into this by magic :)

It turns out that it's a Kotlin feature called delegated properties. In summary, whatever expression you set after by , will be delegated the setting and getting for the variable before the by part. This will be done by calling getValue() / setValue() operators.


class Example {
    var myLazyValue : String by MyOwnLazy()
}

class MyOwnLazy {
    
    private var calculatedValue: String? = null
    
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        if (calculatedValue == null) {
            calculatedValue = calculateValue()
        }
        return calculatedValue!!
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        calculatedValue = value
    }
    
    private fun calculateValue(): String {
        [...]
    }
}

In this super simple example, I kind of re-implemented a lazy value retriever that calculates the value only the first time that it's accessed. The difference from the standard lazy delegate is that it allows being set as well.

Under the hood, Kotlin will create an instance of MyOwnLazy class and call setValue() / getValue() respectively. Notice that in those operators a property parameter is passed that allows access to the object being delegated (in this case myLazyValue).

Standard Delegates  

For not having to reinvent the wheel, Kotlin comes with some standard delegated properties (some of them are more famous than others). A glance at them, in case you find something that might be useful:

  • lazy
    I think the most famous one. You provide a lambda that is used to calculate the value the first time it's accessed.
val lazyValue: String by lazy {
    println("This will be called once")
    "Hello"
}

fun main() {
    println(lazyValue)
    println(lazyValue)
}
  • observable
    You provide an initial value and a lambda. Whenever the value is changed, your lambda is called (thus 'observing' the variable). There's another interesting version of this, called vetoable, that allows you to intercept (and potentially stop) the assignment.
var observed = false
var max: Int by Delegates.observable(0) { property, oldValue, newValue ->
    observed = true
}

println(max) // 0
println("observed is ${observed}") // false

max = 10
println(max) // 10
println("observed is ${observed}") // true
  • map
    Allows you to access map values using variables names as key.
class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}

val user = User(mapOf(
    "name" to "John Doe",
    "age"  to 25
))

println(user.name) // Prints "John Doe". Same as map["name"].
println(user.age)  // Prints 25.  Same as map["age"].

Hopefully, by now you don't consider the by magic and you have seen something interesting you can use in the future!

Show Comments