Advanced Kotlin - Delegates

This post is part of an on-going series discussing some of the more advanced and intermediate features of the Kotlin programming language.

The target audience for this series of posts will be Kotlin developers who are comfortable with the language but would like to learn about the more advanced features that are available. Some of the items I’ll cover over the coming months will be DSLs, reflection, tail recursion, use-site targets, and much more. If this resonates with you, read on!

To kick off the series, we’ll look at delegates in Kotlin. I’ll discuss both the built-in delegates provided by the standard library and also dive into how (and when) to create your own.

Delegates?

But first, what exactly is a property delegate? I guarantee you’ve used a delegate before if you’ve been writing Kotlin for any length of time:

val myString by lazy { "Some Value" }

Here lazy is a property delegate that Kotlin provides us. It’s one of the four built-in delegates from the stdlib, and probably the most used and most well-known. We’ll take a look at the others in a moment.

A delegate is simply a way to wrap common code that will be executed each time you read and/or write from a property value. In this case lazy is used only to construct the value of a field when it is first accessed (aka “lazy loading”).

In the case of lazy this is a top-level Kotlin function that returns an instance of Lazy, which conforms to a very specific interface that allows it to be treated as a delegate by the Kotlin compiler. Each time myString is referenced the code within the stdlib implementation of Lazy gets invoked. It’s simply a means of code-sharing that helps you adhere to the DRY (Don’t Repeat Yourself) good coding practice.

There’s more than just lazy?

Yup. The stdlib actually provides 3 other delegates that are useful to know about.

  • observable
  • vetoable
  • notNull

Let’s look at each of these in turn.

The observable delegate

The next stdlib delegate we’ll look at is observable (not to be confused with RxJava’s Observable). Like the other built-in delegates, this is found in Kotlin’s Delegates class.

Usage is pretty straight-forward:

var maxCount: Int by observable(initialValue = 0) { property, oldValue, newValue ->
   println("${property.name} is being changed from $oldValue to $newValue")
}

This delegate takes two parameters; an initial value and a lambda. The lambda will be executed each time the value of the field is changed.

fun main() {
    maxCount++
    maxCount++
    maxCount += 10
}

> maxCount is being changed from 0 to 1
> maxCount is being changed from 1 to 2
> maxCount is being changed from 2 to 12

This delegate is great if you need to drive one property off of another, log changes (like in the example above), cache old values for undo-style actions, and various other things.

The vetoable delegate

The signature of vetoable looks very much like observable:

var age: Int by vetoable(initialValue = 0) { property, oldValue, newValue ->
   newValue > 0
}

The difference between observable and vetoable is that the lambda parameter for vetoable must return a Boolean. If false is returned then the value will not be modified.

This delegate is a great way to either set reasonable bounds on a value or implement a simple validation framework.

The notNull delegate

notNull is the simplest of the four stdlib delegates. It works similar to lateinit in that it will throw an IllegalStateException if a variable is accessed before it is initialized.

var age by notNull<Int>()

fun main() = println(age)

> Exception in thread "main" java.lang.IllegalStateException: Property age should be initialized before get.

This brings up a good question; what is the difference between notNull and lateinit? When would you have to use notNull when lateinit does the same thing? The answer is that lateinit can only be used on non-primitive types. If you tried to change the above example to “lateinit var age: Int” you would receive the compilation error “‘lateinit’ modifier is not allowed on properties of primitive types”. This is exactly what notNull is meant to solve.

Creating your own delegates

Creating your own delegates is a matter of creating a class that has a specific getValue function for delegates used by val properties, and also a specific setValue function if you want your delegate to also be usable for vars.

The signatures of these functions are as follows:

fun getValue(thisRef: R, property: KProperty<*>): T
fun setValue(thisRef: R, property: KProperty<*>, value: T)

Since the creation of delegates is a compiler feature (vs a runtime feature) you actually don’t need to implement an interface to provide these functions. As long as a class implements these with the operator keyword, the compiler will treat your class as a delegate. However, the stdlib does provide a few interfaces that can be used regardless of this fact. I highly suggest using these interfaces so that future developers looking at your code know that a class is indeed a delegate and the interface also provides a reference for docs. Using the stdlib delegate interfaces also makes it impossible to get the function signatures wrong.

Here are the two interfaces and one abstract class we’ll look at:

The ReadOnlyProperty interface

The ReadOnlyProperty interface only has the getValue() function, so delegates created with it can only be used on val properties.

Let’s create a simple delegate using the ReadOnlyProperty interface and see how it works.

class SimpleReadOnlyDelegate<out T>(private val id: T) : ReadOnlyProperty<Any, T> {
    override fun getValue(thisRef: Any, property: KProperty<*>) = id.also { 
      println("Getting value for ${property.name} from ${thisRef::class.simpleName}") 
    }
}

class DelegateExample {
    val myProperty by SimpleReadOnlyDelegate("demo")
}

fun main() = println(DelegateExample().myProperty)

> Getting value for myProperty from DelegateExample
> "demo"

If you tried to use our SimpleReadOnlyDelegate delegate on a var type the compiler lets you know that the setValue method is missing:

> Missing 'setValue(DelegateExample, KProperty<*>, String)' method on delegate of type 'SimpleReadOnlyDelegate'

We can fix this by changing our class from implementing ReadOnlyProperty to instead implement ReadWriteProperty which contains both getValue and setValue functions.

The ReadWriteProperty interface

If we want to allow both reading and writing values from a delegate we must also include a setValue function. Here’s a very simple delegate that shows using the ReadWriteProperty interface. Note that for brevity this delegate does nothing more than store and return a value. We’ll take a look at some more complex examples further down. For now let’s focus on the basic structure.

class SimpleDelegate<T> : ReadWriteProperty<Any, T?> {
    private var value: T? = null
    override fun getValue(thisRef: Any, property: KProperty<*>) = value
    override fun setValue(thisRef: Any, property: KProperty<*>, value: T?) {
        this.value = value
    }
}

class DelegateDemo {
    var someNum by SimpleDelegate<Int>()
    var someStr by SimpleDelegate<String>()
}

fun main() {
    val demo = DelegateDemo().apply {
        someNum = 42
        someStr = "demo"
    }
    println("${demo.someNum} ${demo.someStr}")
}

> 42 demo

Now that you understand how delegates work and how they are created, you can hopefully see the power that they can provide in helping to encapsulate reusable code.

A few complex examples would include a delegate that stores and retrieves values from a database “var record by MyDBDelegate<String>()”, or on Android a delegate that backs values using SharedPreferences (local storage) “var pref by MySharedPrefsDelegate()”, or a delegate similar to lazy which constructs parts of an object that is expensive to initialize.

The ObservableProperty abstract class

The last item that I’ll cover is an abstract class in the stdlib called ObservableProperty.

If you take a peak at the source code for this class you’ll find that it implements logic that allows you to validate properties in a beforeChange function which is called from setValue. If beforeChange returns false then the property value is not updated. It also provides a (default no-op) afterChange function which is called after setValue is executed.

public abstract class ObservableProperty<T>(initialValue: T) : ReadWriteProperty<Any?, T> {
   private var value = initialValue

   /**
    *  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.
    */
   protected open fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = true

   protected open fun afterChange(property: KProperty<*>, oldValue: T, newValue: T): Unit {}

   public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
       val oldValue = this.value
       if (!beforeChange(property, oldValue, value)) { return }
       this.value = value
       afterChange(property, oldValue, value)
   }

   public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
       return value
   }
}

Does the ability to block property value modification like this sound familiar?

You guessed it; ObservableProperty is exactly how the vetoable delegate that we saw before is implemented in the stdlib…

public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean):
    ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
        override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)
    }

If you find vetoable too restrictive or find that you need to share the logic in a vetoable block accross multiple properties then you should be looking at extending ObservableProperty to create your own delegate instead of using vetoable.

Adding One More Layer

Sometimes you need an extra layer of logic between your property and the creation of a delegate. Kotlin provides a means of wrapping the creation of a delegate via an operator called provideDelegate. This allows you to continue to use the by keyword when defining your property, but the right side of the by will reference a class that returns a delegate, but is not a delegate itself.

class MyDelegateFactory {
    operator fun provideDelegate(thisRef: Any, prop: KProperty<*>): ReadWriteProperty<Any, String> {
        println("returning a new ReadWriteProperty delegate for ${prop.name}")
        return MyDelegate()
    }
}

class MyDelegate: ReadWriteProperty<Any, String> {
    override fun getValue(thisRef: Any, property: KProperty<*>): String { ... }
    override fun setValue(thisRef: Any, property: KProperty<*>, value: String) { ... }
}

class DelegateDemo {
    var myProp by MyDelegateFactory()
}

Another common pattern is to hide the delegate creation behind a top-level function, like we saw with lazy, observable, etc.

Stay tuned! More to come!

Hopefully this article provided enough information on Kotlin delegates for you to get started on building your own, and gave you a few additional items in your tool chest for using observable, vetoable, and notNull.

If you would like to read more about delegates in Kotlin take a look at:

Again, this is the first in a series of “Advanced Kotlin” articles on the American Express Technology Blog. Stay tuned for more!

Happy delegating.