Do something every time a property is set

Hi,

Imagine a Person class with a mutable name where, every time the name is set, some processing happens.

class Person(name: String) {
    var name: String = name
        set(value) {
            field = value
            // do some processing
        }
}

In the above code, however, the processing will not happen the first time name is set, from the initializer. This is a problem for me, as I need the processing to happen every single time name is set, including during initialization.

What is the best way to achieve this in Kotlin?

I realize I could extract “// do some processing” into a method and call this method from both the setter and an init block, but this seems too wordy. Is there something better?

Thanks,
Emma

You could use a custom delegate

var name: String by OnChangeDelegate(initalValue) {
     // do some proessing
}

class OnChangeDelegate<T>(var state: T, val onChange: (T) -> Unit): ReadWriteProperty {
   override operator fun getValue(thisRef: R, property: KProperty<*>): T = state

    override operator fun setValue(thisRef: R, property: KProperty<*>, value: T) {
        state = value
        onChange(state)
    }
}

Thanks,

This is an interesting idea, although it doesn’t appear to call setValue on initialization.

It’s also a little more complex than I was expecting. Doesn’t Kotlin have a built-in way of handling this common scenario?

Woops. I forgot to add a call to onChange in the constructor of OnChangeDelegate so you would need to add that.

The standard library provides both an observable and vetoable delegate but neither executes the onChange code on initialization. Also I wouldn’t really call this a complex implementation. Delegates are quite common in kotlin (the standard library for example provides variations for lazy, observable and maps can also be used as a delegate).

I see.

But what about something like this:

class Person(name: String) {
    var name: String = name
        set(value) {
            field = value
            hash = computeHash(name)
        }
    private var hash: String = computeHash(name)
}

This computes the hash on initialization and then every time name is changed. I guess delegates wouldn’t work here? Would you use the code like the above in this scenario?

I don’t see any reason why they wouldn’t. You just need to be careful about initialization order or maybe use lateinit

class Person(name: String) {
    var name: String by OnChangeDelegate(name) { hash = computeHash(name) }
    private lateinit var hash: String
}

It depends. If you only ever want to compute a hash I probably wouldn’t add the delegate for that alone. But if you use this often I can see using a delegate instead. It depends on how often it would be used. If it is only once in a big codebase it is probably more confusing than helpful, but if you can use it often it will improve readability because then everyone knows directly “this code is executed always on change and on initialization”.