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!