
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") }
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 Injection[^031_0], 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)
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 }
token and attempts are of different types, the behavior of these two properties is nearly identical and can be extracted using property delegation.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 }
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) }
// 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);
}
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.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.TheKPropertytype 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 }
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 } }
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()
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 }
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 parameterTis only used in in-positions, so it has the contravariantinmodifier. TheReadOnlyPropertyinterface uses the type parameterVonly in out-positions, so it has the covariantoutmodifier.
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 } }
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 }
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.object UserPref : PreferenceHolder() { var splashScreenShown: Boolean by bindToPreference(true) var loginAttempts: Int by bindToPreference(0) }
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 }
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() }
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 usingprovideDelegateI could find.
Delegates.notNulllazyDelegates.observableDelegates.vetoableMap<String, T>andMutableMap<String, T>
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. }
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
notNull delegate.var i: Int by Delegates.notNull() var b: Boolean by Delegates.notNull()
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 }
[^031_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.
[^031_2]: Before you do this, consider the fact that there are already many similar libraries, such as PreferenceHolder, which I published years ago.
