# Property Delegation

A property can delegate its accessor logic.

You connect a property to a delegate with the by keyword:

val/var property by delegate

The delegate’s class must contain a getValue() function if the property is a val (read only) or getValue() and setValue() functions if the property is a var (read/write). First consider the read-only case:

// PropertyDelegation/BasicRead.kt
package propertydelegation
import atomictest.eq
import kotlin.reflect.KProperty

class Readable(val i: Int) {
  val value: String by BasicRead()
}

class BasicRead {
  operator fun getValue(
    r: Readable,
    property: KProperty<*>
  ) = "getValue: ${r.i}"
}

fun main() {
  val x = Readable(11)
  val y = Readable(17)
  x.value eq "getValue: 11"
  y.value eq "getValue: 17"
}

value in Readable is delegated to a BasicRead object. getValue() takes a Readable parameter that allows it to access the Readable—when you say by it binds the BasicRead to the whole Readable object. Notice that getValue() accesses i in Readable.

Because getValue() returns a String, the type of value must also be String.

The second getValue() parameter property is of the special type KProperty, and this provides reflective information about the delegated property.

If the delegated property is a var, it must handle both reading and writing, so the delegate class requires both getValue() and setValue():

// PropertyDelegation/BasicReadWrite.kt
package propertydelegation
import atomictest.eq
import kotlin.reflect.KProperty

class ReadWriteable(var i: Int) {
  var msg = ""
  var value: String by BasicReadWrite()
}

class BasicReadWrite {
  operator fun getValue(
    rw: ReadWriteable,
    property: KProperty<*>
  ) = "getValue: ${rw.i}"
  operator fun setValue(
    rw: ReadWriteable,
    property: KProperty<*>,
    s: String
  ) {
    rw.i = s.toIntOrNull() ?: 0
    rw.msg = "setValue to ${rw.i}"
  }
}

fun main() {
  val x = ReadWriteable(11)
  x.value eq "getValue: 11"
  x.value = "99"
  x.msg eq "setValue to 99"
  x.value eq "getValue: 99"
}

The first two setValue() parameters are the same as getValue(), and the third is the value on the right side of the =, which is what we want to set. Both getValue() and setValue() must agree on the type that is read and written, which in this case is String (the type of value in ReadWriteable).

Notice that setValue() accesses i in ReadWriteable, and also msg.

BasicRead.kt and BasicReadWrite.kt do not implement an interface. A class can be used as a delegate if it simply conforms to the convention of having the necessary function(s) with the necessary signature(s). However, you can also implement the ReadOnlyProperty interface, as seen here in BasicRead2:

// PropertyDelegation/BasicRead2.kt
package propertydelegation
import atomictest.eq
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

class Readable2(val i: Int) {
  val value: String by BasicRead2()
  // SAM conversion:
  val value2: String by
  ReadOnlyProperty { _, _ -> "getValue: $i" }
}

class BasicRead2 :
  ReadOnlyProperty<Readable2, String> {
  override operator fun getValue(
    thisRef: Readable2,
    property: KProperty<*>
  ) = "getValue: ${thisRef.i}"
}

fun main() {
  val x = Readable2(11)
  val y = Readable2(17)
  x.value eq "getValue: 11"
  x.value2 eq "getValue: 11"
  y.value eq "getValue: 17"
  y.value2 eq "getValue: 17"
}

Implementing ReadOnlyProperty communicates to the reader that BasicRead2 can be used as a delegate and ensures a proper getValue() definition.

Because ReadOnlyProperty has only a single member function (and it has been defined as a fun interface in the standard library), value2 is defined much more succinctly using a [SAM conversion](javascript:void(0)).

BasicReadWrite.kt can be modified to implement ReadWriteProperty, ensuring proper getValue() and setValue() definitions:

// PropertyDelegation/BasicReadWrite2.kt
package propertydelegation
import atomictest.eq
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

class ReadWriteable2(var i: Int) {
  var msg = ""
  var value: String by BasicReadWrite2()
}

class BasicReadWrite2 :
  ReadWriteProperty<ReadWriteable2, String> {
  override operator fun getValue(
    rw: ReadWriteable2,
    property: KProperty<*>
  ) = "getValue: ${rw.i}"
  override operator fun setValue(
    rw: ReadWriteable2,
    property: KProperty<*>,
    s: String
  ) {
    rw.i = s.toIntOrNull() ?: 0
    rw.msg = "setValue to ${rw.i}"
  }
}

fun main() {
  val x = ReadWriteable2(11)
  x.value eq "getValue: 11"
  x.value = "99"
  x.msg eq "setValue to 99"
  x.value eq "getValue: 99"
}

Thus, a delegate class must contain either or both of the following functions, which are called when the delegated property is accessed:

  1. For reading:

    operator fun getValue(thisRef: T, property: KProperty<*>): V

  2. For writing:

    setValue(thisRef: T, property: KProperty<*>, value: V)

If the delegated property is a val, only the first function is required and ReadOnlyProperty can be implemented using a [SAM conversion](javascript:void(0)).

The parameters are:

  • thisRef: T points to the delegate object, where T is the type of that delegate. If you don’t want to use thisRef in the function, you can effectively disable it by using Any? for T.
  • property: KProperty<*> provides information about the property itself. The most commonly-used is name, which produces the field name of the delegated property.
  • value is the value stored by setValue() into the delegated property. V is the type of that property.

getValue() and setValue() can either be defined by convention, or written as implementations of ReadOnlyProperty or ReadWriteProperty.

To enable access to private elements, nest the delegate class:

// PropertyDelegation/Accessibility.kt
package propertydelegation
import atomictest.eq
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

class Person(
  private val first: String,
  private val last: String
) {
  val name by     // SAM conversion:
  ReadOnlyProperty<Person, String> { _, _ ->
    "$first $last"
  }
}

fun main() {
  val alien = Person("Floopy", "Noopers")
  alien.name eq "Floopy Noopers"
}

Assuming adequate access to the elements in the delegating class, getValue() and setValue() can be written as extension functions:

// PropertyDelegation/Add.kt
package propertydelegation2
import atomictest.eq
import kotlin.reflect.KProperty

class Add(val a: Int, val b: Int) {
  val sum by Sum()
}

class Sum

operator fun Sum.getValue(
  thisRef: Add,
  property: KProperty<*>
) = thisRef.a + thisRef.b

fun main() {
  val addition = Add(144, 12)
  addition.sum eq 156
}

This way you can use an existing class that you are unable to modify or inherit and still delegate a property with it.

Here, when you set the value of the property, the number stored is the Fibonacci number for that value, using the fibonacci() function from the [Recursion](javascript:void(0)) atom:

// PropertyDelegation/FibonacciProperty.kt
package propertydelegation
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import recursion.fibonacci
import atomictest.eq

class Fibonacci :
  ReadWriteProperty<Any?, Long> {
  private var current: Long = 0
  override operator fun getValue(
    thisRef: Any?,
    property: KProperty<*>
  ) = current
  override operator fun setValue(
    thisRef: Any?,
    property: KProperty<*>,
    value: Long
  ) {
    current = fibonacci(value.toInt())
  }
}

fun main() {
  var fib by Fibonacci()
  fib eq 0L
  fib = 22L
  fib eq 17711L
  fib = 90L
  fib eq 2880067194370816120L
}

fib in main() is a local delegated property—it’s defined inside a function rather than a class. A delegated property can also be defined at file scope.

ReadWriteProperty’s first generic argument can be Any? because we never use it to access anything inside Fibonacci, which would require specific type information. Instead we manipulate the current property as we can in any member function.

In most of the examples we’ve seen so far, the first parameter of getValue() and setValue() are of a specific type. Those delegates were tied to that specific type. Sometimes it is possible to create a general-purpose delegate by ignoring the first type as Any?. For example, suppose we’d like to store each delegated String property in a text file named for that property:

// PropertyDelegation/FileDelegate.kt
package propertydelegation
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import checkinstructions.DataFile

class FileDelegate :
  ReadWriteProperty<Any?, String> {
  override fun getValue(
    thisRef: Any?,
    property: KProperty<*>
  ): String {
    val file =
      DataFile(property.name + ".txt")
    return if (file.exists())
      file.readText()
    else ""
  }
  override fun setValue(
    thisRef: Any?,
    property: KProperty<*>,
    value: String
  ) {
    DataFile(property.name + ".txt")
      .writeText(value)
  }
}

This delegate only needs to interact with the file, and doesn’t need anything through thisRef. We ignore thisRef by typing it as Any?, because Any? has no interesting operations. We are interested in property.name, which is the name of the field. Now we can automatically create a file associated with each property and store that property’s data in that file:

// PropertyDelegation/Configuration.kt
package propertydelegation
import checkinstructions.DataFile
import atomictest.eq

class Configuration {
  var user by FileDelegate()
  var id by FileDelegate()
  var project by FileDelegate()
}

fun main() {
  val config = Configuration()
  config.user = "Luciano"
  config.id = "Ramalho47"
  config.project = "MyLittlePython"
  DataFile("user.txt").readText() eq "Luciano"
  DataFile("id.txt").readText() eq "Ramalho47"
  DataFile("project.txt").readText() eq
    "MyLittlePython"
}

Because it can ignore the surrounding type, FileDelegate is reusable.

Exercises and solutions can be found at www.AtomicKotlin.com.