Published July 01, 2019
Mastering Kotlin: Property Delegates
Kotlin Clean-Code Android Design-Patterns
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.
observablevetoablenotNull
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:
- The Kotlin properties package
- The official Delegated Properties docs
- Chapter 7 in Kotlin In Action
Again, this is the first in a series of “Advanced Kotlin” articles on the American Express Technology Blog. Stay tuned for more!
Happy delegating.
About the Author
Recent Articles
Brent Watson
Staff Engineer
Mastering Kotlin: Use-Site Targets
A deep-dive on use-site targets in Kotlin.