hbrown.dev

Welcome to my developer page. Contact me on: henry.g.brown@hey.com

View on GitHub
26 January 2023

Property Delegation in Kotlin – A Comprehensive Guide

by Henry Brown

Property delegation is a powerful feature of the Kotlin language that allows developers to abstract away the logic related to getter and setter methods. This feature helps to create more easily maintainable code and can reduce boilerplate code. In this blog post, we’ll look at how property delegation works in Kotlin and how you can use it to your advantage.

What is Property Delegation?

At its core, property delegation is a way of offloading the responsibility for managing a property from the class that owns it to another class (the delegate). This delegate class will manage the property on behalf of the owner and provide accessors for getting and setting values. In this way, you can define your properties once in a delegate class and then reuse them throughout your entire application.

Kotlin provides two ways to implement property delegation, either by using a custom delegate or by using one of the pre-defined delegates provided by Kotlin. Let’s take a closer look at both approaches.

Custom Delegate

Using a custom delegate allows you to create your own implementation of what should happen when accessing or modifying a property. To do this, you must first define an interface which contains getValue()/setValue() functions which will be used as accessors for retrieving or setting values for your property. Then, you must create a concrete implementation of this interface which contains the logic for actually managing your property. Finally, you must annotate your property with @Delegate annotation specifying an instance of your custom delegate class as its value. Now whenever someone tries to access or modify that particular property, they will be redirected to your custom delegate implementation instead of calling getters/setters directly on that object. Here’s an example:

class Example {
    var customProp: String by CustomDelegate()
}

class CustomDelegate {
    private var value = ""

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("Getting value for ${property.name}")
        return value
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
        println("Setting value for ${property.name} to $newValue")
        value = newValue
    }
}

In this example, the Example class has a property customProp that is delegated to an instance of CustomDelegate. The CustomDelegate class has two functions getValue() and setValue() that define the behavior of the property when it is accessed or modified. These functions are called the property accessors.

You can use the property like this:

val example = Example()
example.customProp = "Hello"
println(example.customProp)

The output will be:

Setting value for customProp to Hello
Getting value for customProp
Hello

As you can see, the setter and getter methods on the custom delegate are called when the property is set or accessed. You can implement any custom logic inside those methods, for example, you can use this pattern to add some validation rules, cache, or even encrypt/decrypt the values before storing/retrieving it.

Note that custom delegates should follow the same rules as properties, they should be val if the property is read-only and var if the property is read-write.

This is a simple example, but in practice, you could use a custom delegate to abstract away the details of how the property is stored and accessed, like in the case of accessing a value from a database, a file, or a remote service.

Pre-Defined Delegates

Kotlin also provides several pre-defined delegates which make it easy to implement common scenarios without having to write any additional code yourself. For example, lazy() makes it easy to delay initializing properties until they are actually needed; observable() makes it easy to react when properties are modified; vetoable() allows you to prevent certain modifications from taking effect; Each of these delegates comes with their own constructors which allow you to customise their behavior according to your needs.

Here’s an example of using the vetoable() property delegate:

class Example {
    var age: Int by vetoable(0) { _, _, newValue ->
        newValue >= 18
    }
}

fun main() {
    val example = Example()
    example.age = 17
    println(example.age) // prints 0
    example.age = 18
    println(example.age) // prints 18
}

The Example class has a property age that is delegated to the vetoable function. The vetoable function takes an initial value and a lambda that defines a validation function that will be called every time the property is set. The validation function takes 3 parameters:

  1. the object containing the property,
  2. the property itself and
  3. the new value.

In this case, the validation function checks if the new value is greater than or equal to 18. If the validation function returns true, the new value is set, otherwise, the old value is kept.

You can see that when the value of age is set to 17, the validation fails, so the old value (0) is kept. When the value of age is set to 18, the validation is passed and the new value is set.

the vetoable function can be used when you want to add some validation rules before setting the value of a property, and you want to keep the old value if the validation fails.

Here’s an example of how you can use the observable property delegate in Kotlin:

class Example {
    var name: String by observable("") { _, oldValue, newValue ->
        println("Name changed from [$oldValue] to [$newValue]")
    }
}

fun main() {
    val example = Example()
    example.name = "John"
    example.name = "Jane"
}

In this example, the Example class has a property name that is delegated to the observable function. The observable function takes:

  1. an initial value and
  2. a lambda that defines a callback function that will be called every time the property is set.

The callback function takes 3 parameters:

  1. the object containing the property,
  2. the property itself and
  3. the new value.

In this case, the callback function prints a message that shows the old value and the new value of the property every time it changes:

Name changed from [] to [John]
Name changed from [John] to [Jane]

The observable function can be used when you want to be notified every time the value of a property changes.

The final example demonstrates how you can use the lazy property delegate in Kotlin:

class Example {
    val heavyObject: HeavyObject by lazy {
        println("Creating heavy object...")
        HeavyObject()
    }
}

class HeavyObject {
    // Some heavy computations or resource-intensive initialization
}

fun main() {
    val example = Example()
    println("Accessing heavy object for the first time...")
    val obj1 = example.heavyObject
    println("Accessing heavy object for the second time...")
    val obj2 = example.heavyObject
}

In this example, the Example class has a property heavyObject that is delegated to the lazy function. The lazy function takes a lambda that defines the initialization function for the property.

The initialization function is invoked the first time the property is accessed and the returned value is cached and returned for subsequent accesses. The lazy delegate guarantees that the initialization function is executed only once.

In this case, when you run the program you’ll see the following output:

Accessing heavy object for the first time...
Creating heavy object...
Accessing heavy object for the second time...

You can see that the first time the heavyObject property is accessed, the initialization function is called and the message Creating heavy object... is printed. The second time the heavyObject property is accessed, the cached value is returned and the initialization function is not called again.

The lazy function can be used when you want to delay the initialization of a property until it’s actually used, or when the initialization is expensive, and you want to avoid doing it multiple times.

Conclusion:

Property delegation is an incredibly powerful feature in Kotlin that helps developers create more maintainable code with less boilerplate code than traditional Java implementations require. By delegating responsibility for managing properties onto other classes either through custom delegates or pre-defined delegates provided by Kotlin, developers can dramatically reduce the amount of time spent writing repetitive getter/setter methods while still maintaining control over their data model objects and ensuring their data remains consistent across all parts of their application. Whether you are working on new projects or refactoring existing ones, property delegation should definitely be considered as part of any software development project written in Kotlin!

As usual, all the code examples are available on my Github repository.

tags: kotlin - delegation