article banner

Property delegation

This is a chapter from the book Advanced Kotlin. You can find it on LeanPub or Amazon.

In programming, there are a number of patterns that use properties, like lazy properties, property value injection, property binding, etc. In most languages, there is no easy way to extract such patterns, therefore developers tend to repeat the same patterns again and again, or they need to depend on complex libraries. In Kotlin, we extract repeatable property patterns using a feature that is (currently) unique to Kotlin: property delegation. This feature’s trademark is the by keyword, which is used between a property definition and a delegate specification. Together with some functions from Kotlin stdlib, here is an example usage of property delegation that we use to implement lazy or observable properties:

val value by lazy { createValue() } var items: List<Item> by Delegates.observable(listOf()) { _, _, _ -> notifyDataSetChanged() } var key: String? by Delegates.observable(null) { _, old, new -> Log.e("key changed from $old to $new") }

Later in this chapter, we will discuss both lazy and observable functions in detail. For now, all you need to know is that delegates are just functions; they are not Kotlin keywords (just like lazy in Scala or Swift). We can implement our own lazy function in a few simple lines of code. Just like many Kotlin libraries, we can also implement our own property delegates. Some good examples are View and Resource Binding, Dependency Injection0, and Data Binding.

// View and resource binding example in Android private val button: Button by bindView(R.id.button) private val textSize by bindDimension(R.dimen.font_size) private val doctor: Doctor by argExtra(DOCTOR_ARG) // Dependency Injection using Koin private val presenter: MainPresenter by inject() private val repository: NetworkRepository by inject() private val viewModel: MainViewModel by viewModel() // Data binding private val port by bindConfiguration("port") private val token: String by preferences.bind(TOKEN_KEY)

How property delegation works

To understand how we can extract other common behaviors using property delegation, let’s start with a very simple property delegate. Let’s say we need to define properties with custom getters and setters that print their value changes1:

var token: String? = null get() { println("token getter returned $field") return field } set(value) { println("token changed from $field to $value") field = value } var attempts: Int = 0 get() { println("attempts getter returned $field") return field } set(value) { println("attempts changed from $field to $value") field = value } fun main() { token = "AAA" // token changed from null to AAA val res = token // token getter returned AAA println(res) // AAA attempts++ // attempts getter returned 0 // attempts changed from 0 to 1 }

Even though token and attempts are of different types, the behavior of these two properties is nearly identical and can be extracted using property delegation.

Property delegation is based on the idea that a property is defined by its accessors: for val, it is a getter; for var, it is a getter and a setter. These functions can be delegated to another object’s methods:the getter will be delegated to the getValue function, and the setter to the setValue function. An object with these methods needs to be created and placed on the right side of the by keyword. To make our properties behave the same way as in the example above, we can create the following delegate:

import kotlin.reflect.KProperty private class LoggingProperty<T>(var value: T) { operator fun getValue(thisRef: Any?, prop: KProperty<*>): T { println("${prop.name} getter returned $value") return value } operator fun setValue( thisRef: Any?, prop: KProperty<*>, newValue: T ) { println("${prop.name} changed from $value to $newValue") value = newValue } } var token: String? by LoggingProperty(null) var attempts: Int by LoggingProperty(0) fun main() { token = "AAA" // token changed from null to AAA val res = token // token getter returned AAA println(res) // AAA attempts++ // attempts getter returned 0 // attempts changed from 0 to 1 }

To fully understand how property delegation works, take a look at what by is compiled to. The above token property will be compiled to something similar to the following code:

// Code in Kotlin: var token: String? by LoggingProperty(null) // What it is compiled to when a property is top-level @JvmField private val `token$delegate` = LoggingProperty<String?>(null) var token: String? get() = `token$delegate`.getValue(null, ::token) set(value) { `token$delegate`.setValue(null, ::token, value) } // What it is compiled to when a property is in a class @JvmField private val `token$delegate` = LoggingProperty<String?>(null) var token: String? get() = `token$delegate`.getValue(this, this::token) set(value) { `token$delegate`.setValue(this, this::token, value) }

To make sure we understand this, let's look under the hood and check out what our Kotlin property delegate usage is compiled to.

// Kotlin code: var token: String? by LoggingProperty(null) fun main() { token = "AAA" // token changed from null to AAA val res = token // token getter returned AAA println(res) // AAA }
// Java representation of this code:
@Nullable
private static final LoggingProperty token$delegate =
   new LoggingProperty((Object)null);

@Nullable
public static final String getToken() {
   return (String)token$delegate
       .getValue((Object)null, $$delegatedProperties[0]);
}

public static final void setToken(@Nullable String var0) {
   token$delegate
     .setValue((Object)null, $$delegatedProperties[0], var0);
}

public static final void main() {
    setToken("AAA");
    String res = getToken();
    System.out.println(res);
}

Let's analyze this step by step. When you get a property value, you call this property's getter; property delegation delegates this getter to the getValue function. When you set a property value, you are calling this property's setter; property delegation delegates this setter to the setValue function. This way, each delegate fully controls this property’s behavior.

Other getValue and setValue parameters

You might also have noticed that the getValue and setValue methods not only receive the value that was set to the property and decide what its getter returns, but they also receive a bounded reference to the property as well as a context (this). The reference to the property is most often used to get its name and sometimes to get information about annotations. The parameter referencing the receiver gives us information about where the function is used and who can use it.

The KProperty type will be better covered later in the Reflection chapter.

import kotlin.reflect.KProperty private class LoggingProperty<T>( var value: T ) { operator fun getValue( thisRef: Any?, prop: KProperty<*> ): T { println("${prop.name} in $thisRef getter returned $value") return value } operator fun setValue( thisRef: Any?, prop: KProperty<*>, newValue: T ) { println("${prop.name} in $thisRef changed " + "from $value to $newValue") value = newValue } } var token: String? by LoggingProperty(null) object AttemptsCounter { var attempts: Int by LoggingProperty(0) } fun main() { token = "AAA" // token in null changed from null to AAA val res = token // token in null getter returned AAA println(res) // AAA AttemptsCounter.attempts = 1 // attempts in AttemptsCounter@XYZ changed from 0 to 1 val res2 = AttemptsCounter.attempts // attempts in AttemptsCounter@XYZ getter returned 1 println(res2) // 1 }

When we have multiple getValue and setValue methods but with different context types, different definitions of the same method will be chosen in different situations. This fact can be used in clever ways. For instance, we might need a delegate that can be used in different kinds of views, but it should behave differently with each of them based on what is offered by the context:

class SwipeRefreshBinderDelegate(val id: Int) { private var cache: SwipeRefreshLayout? = null operator fun getValue( activity: Activity, prop: KProperty<*> ): SwipeRefreshLayout = cache ?: activity .findViewById<SwipeRefreshLayout>(id) .also { cache = it } operator fun getValue( fragment: Fragment, prop: KProperty<*> ): SwipeRefreshLayout = cache ?: fragment.view .findViewById<SwipeRefreshLayout>(id) .also { cache = it } }

Implementing a custom property delegate

To make it possible to use an object as a property delegate, all it needs is the getValue operator for val and the getValue and setValue operators for var. Both getValue and setValue are operators, so they need the operator modifier. They need parameters for thisRef of any type (most likely Any?) and property of type KProperty<*>. setValue should additionally have a property for a value whose type should be the same type or a supertype of the type used by the property. The getValue result type should be the same type or a subtype of the type used by the property.

class EmptyPropertyDelegate { operator fun getValue( thisRef: Any?, property: KProperty<*> ): String = "" operator fun setValue( thisRef: Any?, property: KProperty<*>, value: String ) { // no-op } } val p1: String by EmptyPropertyDelegate() var p2: String by EmptyPropertyDelegate()

These methods can be member functions, but they can also be extension functions. For instance, Map<String, *> can be used as a property delegate thanks to the extension function below, which is defined in the standard library. We will discuss using Map<String, *> as a delegate later in this chapter.

// Function from Kotlin stdlib inline operator fun <V, V1 : V> Map<in String, V>.getValue( thisRef: Any?, property: KProperty<*> ): V1 = getOrImplicitDefault(property.name) as V1 fun main() { val map: Map<String, Any> = mapOf( "name" to "Marcin", "kotlinProgrammer" to true ) val name: String by map val kotlinProgrammer: Boolean by map print(name) // Marcin print(kotlinProgrammer) // true val incorrectName by map println(incorrectName) // Exception }

When we define a delegate, it might be helpful to implement the ReadOnlyProperty interface (when we define a property delegate for val) or the ReadWriteProperty interface (when we define a property delegate for var) from Kotlin stdlib. These interfaces specify getValue and setValue with the correct parameters.

fun interface ReadOnlyProperty<in T, out V> { operator fun getValue( thisRef: T, property: KProperty<*> ): V } interface ReadWriteProperty<in T, V>: ReadOnlyProperty<T,V> { override operator fun getValue( thisRef: T, property: KProperty<*> ): V operator fun setValue( thisRef: T, property: KProperty<*>, value: V ) }

Notice how those interfaces use generic variance modifiers. Type parameter T is only used in in-positions, so it has the contravariant in modifier. The ReadOnlyProperty interface uses the type parameter V only in out-positions, so it has the covariant out modifier.

Both ReadOnlyProperty and ReadWriteProperty require two type arguments. The first is for the receiver type and is typically Any?, which allows our property delegate to be used in any context. The second argument should be the property's value type. If we define a property delegate for properties of a certain type, we set this type here. We can also use a generic type parameter in this type argument position.

private class LoggingProperty<T>( var value: T ) : ReadWriteProperty<Any?, T> { override fun getValue(thisRef: Any?, prop: KProperty<*>): T { println("${prop.name} getter returned $value") return value } override fun setValue( thisRef: Any?, prop: KProperty<*>, newValue: T ) { println("${prop.name} changed from $value to $newValue") this.value = newValue } }

Provide a delegate

This is a story as old as time. You delegate a task to another person, who, instead of doing it, delegates it to someone else. Objects in Kotlin can do the same. An object can define the provideDelegate method, which returns another object that will be used as a delegate.

import kotlin.reflect.KProperty class LoggingProperty<T>(var value: T) { operator fun getValue(thisRef: Any?, prop: KProperty<*>): T { println("${prop.name} getter returned $value") return value } operator fun setValue( thisRef: Any?, prop: KProperty<*>, newValue: T ) { println("${prop.name} changed from $value to $newValue") value = newValue } } class LoggingPropertyProvider<T>( private val value: T ) { operator fun provideDelegate( thisRef: Any?, property: KProperty<*> ): LoggingProperty<T> = LoggingProperty(value) } var token: String? by LoggingPropertyProvider(null) var attempts: Int by LoggingPropertyProvider(0) fun main() { token = "AAA" // token changed from null to AAA val res = token // token getter returned AAA println(res) // AAA }

The power of provideDelegate is that the object that is used on the right side of the by keyword does not need to be used as a delegate. So, for instance, an immutable object can provide a mutable delegate.

Let's see an example. Let's say you implement a library to make it easier to operate on values stored in preference files2. This is how you want your library to be used:

object UserPref : PreferenceHolder() { var splashScreenShown: Boolean by bindToPreference(true) var loginAttempts: Int by bindToPreference(0) }

The bindToPreference function returns an object that can be used as a property delegate. But what if we want to make it possible to use Int or Boolean as delegates in the scope of PreferenceHolder?

object UserPref : PreferenceHolder() { var splashScreenShown: Boolean by true var loginAttempts: Int by 0 }

This way, using delegates is similar to assigning some values; however, because delegates are used, some additional operations might be performed, such as binding these property values to preference file values.

It is problematic to use Int or Boolean as delegates because they are not implemented to be used this way. We could implement getValue and setValue to operate on preference files, but it would be harder to cache values. The simple solution is to define the provideDelegate extension function on Int and Boolean. This way, Int or Boolean can be used on the right side of by, but they can’t be used as delegates themselves.

abstract class PreferenceHolder { operator fun Boolean.provideDelegate( thisRef: Any?, property: KProperty<*> ) = bindToPreference(this) operator fun Int.provideDelegate( thisRef: Any?, property: KProperty<*> ) = bindToPreference(this) inline fun <reified T : Any> bindToPreference( default: T ): ReadWriteProperty<PreferenceHolder, T> = TODO() }

We can also use the PropertyDelegateProvider interface, which specifies the provideDelegate function with appropriate arguments and result types. The two type parameters of PropertyDelegateProvider represent the receiver reference type and the type of the property we delegate.

class LoggingPropertyProvider<T>( private val value: T ) : PropertyDelegateProvider<Any?, LoggingProperty<T>> { override fun provideDelegate( thisRef: Any?, property: KProperty<*> ): LoggingProperty<T> = LoggingProperty(value) }

Personally, I am not a fan of using raw values as delegates because I believe that additional function names improve readability. However, this is the best example of using provideDelegate I could find.

Property delegates in Kotlin stdlib

Kotlin provides the following standard property delegates:

  • Delegates.notNull
  • lazy
  • Delegates.observable
  • Delegates.vetoable
  • Map<String, T> and MutableMap<String, T>

Let's discuss them individually and present their use cases.

The notNull delegate

I will start with the simplest property delegate, which is created using the notNull method from the Delegates object that is defined in Kotlin stdlib. It is an alternative to lateinit, so the property delegated to notNull behaves like a regular property but has no initial value. Therefore, if you try to get a value before setting it, this results in an exception.

import kotlin.properties.Delegates var a: Int by Delegates.notNull() var b: String by Delegates.notNull() fun main() { a = 10 println(a) // 10 a = 20 println(a) // 20 println(b) // IllegalStateException: // Property b should be initialized before getting. }

Wherever possible, we should use the lateinit property instead of the notNull delegate for better performance because lateinit properties are faster. Currently, however, Kotlin does not support lateinit properties with types that associate with primitives, like Int or Boolean.

lateinit var i: Int // Compilation Error lateinit var b: Boolean // Compilation Error

In such cases, we use the notNull delegate.

var i: Int by Delegates.notNull() var b: Boolean by Delegates.notNull()

I often see this delegate used as part of DSL builders or for properties whose values are injected.

abstract class IntegrationTest { @Value("${server.port}") var serverPort: Int by Delegates.notNull() // ... } // DSL builder fun person(block: PersonBuilder.() -> Unit): Person = PersonBuilder().apply(block).build() class PersonBuilder() { lateinit var name: String var age: Int by Delegates.notNull() fun build(): Person = Person(name, age) } // DSL use val person = person { name = "Marc" age = 30 }

In the next part of this series, you will see other property delegates from Kotlin stdlib, together with their use cases. So, stay tuned!

0:

This example use of Koin formally presents service location, not dependency injection.

1:

I assume you are familiar with custom getters and setters. I explain them in the previous book from this series, Kotlin Essentials, in the Classes chapter.

2:

Before you do this, consider the fact that there are already many similar libraries, such as PreferenceHolder, which I published years ago.