diff --git a/libraries/stdlib/src/kotlin/properties/Delegation.kt b/libraries/stdlib/src/kotlin/properties/Delegation.kt index a90fed1fac7..c492c3196c6 100644 --- a/libraries/stdlib/src/kotlin/properties/Delegation.kt +++ b/libraries/stdlib/src/kotlin/properties/Delegation.kt @@ -36,24 +36,24 @@ public object Delegates { /** * Returns a property delegate for a read/write property that calls a specified callback function when changed. * @param initial the initial value of the property. - * @param onChange the callback which is called when the property value is changed. + * @param onChange the callback which is called after the change of the property is made. The value of the property + * has already been changed when this callback is invoked. */ - public fun observable(initial: T, onChange: (desc: PropertyMetadata, oldValue: T, newValue: T) -> Unit): ReadWriteProperty { - return ObservableProperty(initial) { desc, old, new -> - onChange(desc, old, new) - true - } + public fun observable(initial: T, onChange: (property: PropertyMetadata, oldValue: T, newValue: T) -> Unit): ReadWriteProperty { + return ObservableProperty(initial, afterChange = onChange) } /** * Returns a property delegate for a read/write property that calls a specified callback function when changed, * allowing the callback to veto the modification. * @param initial the initial value of the property. - * @param onChange the callback which is called when a change to the property value is attempted. The new value - * is saved if the callback returns `true` and discarded if the callback returns `false`. + * @param onChange the callback which is called before a change to the property value is attempted. + * The value of the property hasn't been changed yet, when this callback is invoked. + * If the callback returns `true` the value of the property is being set to the new value, + * and if the callback returns `false` the new value is discarded and the property remains its old value. */ - public fun vetoable(initial: T, onChange: (desc: PropertyMetadata, oldValue: T, newValue: T) -> Boolean): ReadWriteProperty { - return ObservableProperty(initial, onChange) + public fun vetoable(initial: T, onChange: (property: PropertyMetadata, oldValue: T, newValue: T) -> Boolean): ReadWriteProperty { + return ObservableProperty(initial, beforeChange = onChange) } /** @@ -115,11 +115,17 @@ private class NotNullVar() : ReadWriteProperty { /** * Implements a property delegate for a read/write property that calls a specified callback function when changed. * @param initialValue the initial value of the property. - * @param onChange the callback which is called when a change to the property value is attempted. The new value -* is saved if the callback returns `true` and discarded if the callback returns `false`. + * @param beforeChange the callback which is called before a change to the property value is attempted. + * The value of the property hasn't been changed yet, when this callback is invoked. + * If the callback returns `true` the value of the property is being set to the new value, + * and if the callback returns `false` the new value is discarded and the property remains its old value. + * @param afterChange the callback which is called after the change of the property is made. The value of the property + * has already been changed when this callback is invoked. */ public class ObservableProperty( - initialValue: T, private val onChange: (name: PropertyMetadata, oldValue: T, newValue: T) -> Boolean + initialValue: T, + private val beforeChange: ((property: PropertyMetadata, oldValue: T, newValue: T) -> Boolean)? = null, + private val afterChange: ((property: PropertyMetadata, oldValue: T, newValue: T) -> Unit)? = null ) : ReadWriteProperty { private var value = initialValue @@ -128,9 +134,17 @@ public class ObservableProperty( } public override fun set(thisRef: Any?, property: PropertyMetadata, value: T) { - if (onChange(property, this.value, value)) { + if (beforeChange == null && afterChange == null) { this.value = value } + else { + val oldValue = this.value + if (beforeChange != null && !(beforeChange)(property, oldValue, value)) { + return + } + this.value = value + if (afterChange != null) (afterChange)(property, oldValue, value) + } } } diff --git a/libraries/stdlib/test/properties/delegation/DelegationTest.kt b/libraries/stdlib/test/properties/delegation/DelegationTest.kt index 1f141f641b8..43f3984f824 100644 --- a/libraries/stdlib/test/properties/delegation/DelegationTest.kt +++ b/libraries/stdlib/test/properties/delegation/DelegationTest.kt @@ -48,7 +48,11 @@ class ObservablePropertyInChangeSupportTest: ChangeSupport() { class ObservablePropertyTest { var result = false - var b by Delegates.observable(1, { pd, o, n -> result = true}) + var b: Int by Delegates.observable(1, { property, old, new -> + assertEquals("b", property.name) + result = true + assertEquals(new, b, "New value has already been set") + }) test fun doTest() { b = 4 @@ -61,7 +65,12 @@ class A(val p: Boolean) class VetoablePropertyTest { var result = false - var b by Delegates.vetoable(A(true), { pd, o, n -> result = n.p == true; result}) + var b: A by Delegates.vetoable(A(true), { property, old, new -> + assertEquals("b", property.name) + assertEquals(old, b, "New value hasn't been set yet") + result = new.p == true; + result + }) test fun doTest() { val firstValue = A(true)