I love this Swift syntax; it's very helpful for many things:
var foo: Bar = Bar() {
willSet {
baz.prepareToDoTheThing()
}
didSet {
baz.doTheThing()
}
}
and I'd love to do this in Kotlin. However, I can't find the proper syntax!
Is there anything in Kotlin like this?
I expected the syntax to look like this, but I can't find anything of the sort:
var foo: Bar = Bar()
willSet() {
baz.prepareToDoTheThing()
}
didSet() {
baz.doTheThing()
}
Although Kotlin doesn't provide a built-in Swift-style solution for property changes observation, you can still do it in several ways depending on what your goal is.
There is observable(...)
delegate (in stdlib) that allows you to handle the property changes. Usage example:
var foo: String by Delegates.observable("bar") { property, old, new ->
println("$property has changed from $old to $new")
}
Here, "bar"
is the initial value for property foo
, and the lambda is called each time after the property is assigned, allowing you to observe the changes.There's also vetoable(...)
delegate that allows you to prevent the change.
You can use custom setter for a property to execute arbitrary code before/after actual value change:
var foo: String = "foo"
set(value: String) {
baz.prepareToDoTheThing()
field = value
baz.doTheThing()
}
As @KirillRakhman noted, this solution is quite efficient since it introduces no overhead in method calls and objects, though the code will be a little duplicated in case of several properties.
In general, you can implement your own property delegate, explicitly providing the property behavior in getValue(...)
and setValue(...)
functions.
To simplify your task, use ObservableProperty<T>
abstract class that allows you to implement delegates that observe property changes (like observable
and vetoable
above) Example:
var foo: String by object : ObservableProperty<String>("bar") {
override fun beforeChange(property: KProperty<*>, oldValue: String, newValue: String): Boolean {
baz.prepareToDoTheThing()
return true // return false if you don't want the change
}
override fun afterChange(property: KProperty<*>, oldValue: String, newValue: String) {
baz.doTheThing()
}
}
For your convenience, you can write a function that creates the delegate object:
fun <T> observing(initialValue: T,
willSet: () -> Unit = { },
didSet: () -> Unit = { }
) = object : ObservableProperty<T>(initialValue) {
override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean =
true.apply { willSet() }
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = didSet()
}
Then you just pass lambdas to it as willSet
and didSet
(the default argument for them is { }
). Usage:
var foo: String by observing("bar", willSet = {
baz.prepareToDoTheThing()
}, didSet = {
baz.doTheThing()
})
var baq: String by observing("bar", didSet = { println(baq) })
In any case, it's up to you to make sure that the code observing the changes doesn't set the property again as it will likely fall into infinite recursion, or else you might check it in the observing code whether the setter is called recursively.