Other Stock Property Delegates
There are a few classes in the Kotlin standard library, besides Lazy
, that are set up to serve as property delegates.
Map
and MutableMap
Map
has the getValue()
function needed to serve as a val
property delegate, so we can delegate a property to a Map
:
val stuff = mapOf("something" to 1337, "somethingElse" to "like, whatever")
val something: Int by stuff
val somethingElse: String by stuff
println(something)
println(somethingElse)
Here, we use by stuff
to delegate our properties to a Map
named stuff
. When we go to read those properties, the Map
will look up the corresponding values, based on the property names, and return them.
Delegates
The Delegates
class offers additional property delegates.
For example, Delegates.observable()
allows you to provide a callback that will be invoked when the property value changes:
import kotlin.properties.Delegates
fun main() {
var observed: String by Delegates.observable("initial value") { property, oldValue, newValue ->
println("'${property.name}' changed from '$oldValue' to '$newValue'")
}
println(observed)
observed = "new value"
println(observed)
}
Your callback receives the old and new property value, along with an object representing the property itself. That is a KProperty
object, which we use here to get the property name.
There is also vetoable()
:
import kotlin.properties.Delegates
fun main() {
var noOddValuesPlease: Int by Delegates.vetoable(2) { property, oldValue, newValue ->
newValue % 2 == 0
}
println(noOddValuesPlease)
noOddValuesPlease = 4
println(noOddValuesPlease)
noOddValuesPlease = 3
println(noOddValuesPlease)
}
This time, the lambda expression needs to return a Boolean
. If it returns true
, then the property value will be set to the new value. If it returns false
, though, then the property value will be left alone. Therefore, the lambda expression can “veto” a change that it does not like. In this case, we veto any change that results in an odd number, giving us:
2
4
4
Delegating Properties to Properties
Kotlin 1.4 added a new feature: allowing you to delegate a property to another property.
Off the cuff, that may sound silly.
However, it is particularly useful for dealing with API changes, especially in situations where you cannot easily change the consumers of that API, such as a library that is reused by lots of projects.
Suppose, for example, that you decide that your mis-named a public property and want to switch to a different name. Perhaps you have:
class SomethingCool {
var oldPropertyName: String = "default"
}
…and now you want:
class SomethingCool {
var newPropertyName: String = "default"
}
The problem is that all existing users of your API are using the old name.
You can use property delegation to “rewire” the old API to really use your new property:
class SomethingCool {
var newPropertyName: String = "default"
var oldPropertyName by this::newPropertyName
}
Now, anything still using oldPropertyName
still works.
You can combine this with the @Deprecated
annotation to steer developers towards the new name:
class SomethingCool {
var newPropertyName: String = "default"
@Deprecated("Please use 'newPropertyName' instead", ReplaceWith("newPropertyName"))
var oldPropertyName by this::newPropertyName
}
Here, we scope the reference using this::
to refer to a property on the receiver itself. We could route it to some other object, if needed:
class SomethingCool(val otherCoolThing: SomethingElseCool) {
var oldPropertyName by otherCoolThing::whatever
}
Now, references to oldPropertyName
on the SomethingCool
wind up actually being handled by the whatever
property on a SomethingElseCool
object that was supplied to the SomethingCool
constructor.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.