64

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?

var foo: Bar = Bar()
    willSet() {
        baz.prepareToDoTheThing()
    }
    didSet() {
        baz.doTheThing()
    }
hotkey
  • 140,743
  • 39
  • 371
  • 326
Ky -
  • 30,724
  • 51
  • 192
  • 308

1 Answers1

98

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.

Community
  • 1
  • 1
hotkey
  • 140,743
  • 39
  • 371
  • 326
  • 3
    Amazingly, that second approach is exactly the workaround I was building between me asking and you answering! :D – Ky - Oct 04 '16 at 01:19
  • 3
    @hotkey You should elaborate on the custom setter a bit since it's the most efficient way to implement it. – Kirill Rakhman Oct 04 '16 at 07:36
  • 3
    @KirillRakhman, thanks for your remark, I've updated the answer. – hotkey Oct 04 '16 at 14:10
  • 1
    Great answer, but IMO `true.apply { willSet() }` is not idiomatic. I would change this to a code block with a return statement. – Ingo Kegel Oct 05 '16 at 08:00
  • custom setters is the exact analog of this Swift concept – voddan Oct 06 '16 at 12:09
  • 4
    @voddan, seems like Swift has `get`, `set`, `didSet`, `willSet`, and Kotlin doesn't have an exact equivalent for `didSet` and `willSet` (which are what the question is about). – hotkey Oct 06 '16 at 12:47
  • @voddan see: http://stackoverflow.com/questions/24006234/what-is-the-purpose-of-willset-and-didset-in-swift – Ky - Oct 08 '16 at 01:50
  • The function you wrote works perfectly; thanks for the effort. – JillevdW Jul 31 '18 at 13:45
  • @IngoKegel similar thing immediately I had in my mind that this is not readable easily for me... the returning value is written first and code that is run before is after in connected braces... Imho its nonsense to write code that is harder to understand just to save two lines. – Renetik Apr 23 '21 at 18:56