Learning Kotlin: The Observable Delegate (with a slight detour on reference functions)

Submitted by Robert MacLean on Wed, 08/29/2018 - 09:00

More Information

  • This is the 27th post in a multipart series.
    If you want to read more, see our series index
  • Koans used in here are 34 and 35

Following on from our introduction to the by operator and delegates, this post looks at the second of the five built-in delegates, observable.

The next built-in delegate is observable - this allows intercept all attempts to set the value. You can do this with a setter, but remember you would need to duplicate that setter code every time. With the observable, you can build the logic once and reuse it over and over again.

Once again let us start with the way we would do this without the delegated property:

  1. class User() {
  2.     var name: String = "<NO VALUE>"
  3.         set(value) {
  4.             DataChanged("name", name, value)
  5.         }
  6.  
  7.     var eyeColour: String = "<NO VALUE>"
  8.         set(value) {
  9.             DataChanged("eyeColour", name, value)
  10.         }
  11.  
  12.     fun DataChanged(propertyName: String, oldValue: String, newValue: String) {
  13.         println("$propertyName changed! $oldValue -> $newValue")
  14.     }
  15. }
  16.  
  17. fun main(args:Array<String>) {
  18.     val user = User()
  19.     user.name = "Robert"
  20.     user.eyeColour = "Green"
  21. }

Note, that we need to do the setter manually twice and in each one we will need to change a value, which you know you will get missed when you copy & paste the code.

In the next example, we change to use the observable delegate which allows us to easily call the same function.

While I don't recommend this for production, I did in the example call it in two different ways. For age, as the second parameter is a lambda I just create that and pass the parameters to my function. This is how all the demos normally show the usage of this. For name though, since my function has the same signature as the lambda I can pass it directly to the observable which seems MUCH nicer to me. Though since we need to pass a reference to our function we need to prefix it with ::.

  1. package sadev
  2.  
  3. import kotlin.properties.Delegates
  4. import kotlin.reflect.KProperty
  5.  
  6. class User() {
  7.     var name: String by Delegates.observable("<NO VALUE>", ::DataChanged)
  8.  
  9.     var eyeColour: String by Delegates.observable("<NO VALUE>") { property, old, new ->
  10.             DataChanged(property, old, new)
  11.         }
  12.  
  13.     fun DataChanged(property: KProperty<*>, oldValue: String, newValue: String) {
  14.         println("${property.name} changed! $oldValue -> $newValue")
  15.     }
  16. }
  17.  
  18. fun main(args:Array<String>) {
  19.     val user = User()
  20.     user.name = "Robert"
  21.     user.eyeColour = "Green"
  22. }