A Guide to Kotlin Delegation & Delegated Properties for Flexible and Efficient Development

Table of Contents

Kotlin offers various powerful features to make code concise and efficient. One such feature is delegation, which allows you to delegate the implementation of properties or functions to another object. This concept of delegation plays a crucial role in achieving code reuse, separation of concerns, and enhancing the readability and maintainability of your code. In this blog, we will explore Kotlin delegation and delve into the details of delegated properties.

Delegated properties allow you to leverage the power of trusted helper objects called delegates. These delegates handle complex tasks, freeing up your properties to focus on their core responsibilities. From database tables to browser sessions and maps, the possibilities are endless.

Join me as we embark on this thrilling journey, exploring the art of delegation and unlocking the true potential of Kotlin delegation properties. Get ready to witness the magic as your properties become extraordinary with just a touch of delegation!

Understanding Kotlin Delegation

Delegation in programming is a design pattern where an object, known as the delegate, is given the responsibility to handle certain tasks or operations on behalf of another object, known as the delegator. The delegator object delegates the work to the delegate object, which performs the task and returns the result to the delegator.

Let’s use a real-life example to understand delegation in Kotlin. Consider a scenario where you have a restaurant with a customer, a waiter, and a chef. The customer wants to order a meal, and the waiter is responsible for taking the order and delivering it to the chef. The chef prepares the meal and hands it back to the waiter, who serves it to the customer.

In this example, the customer is the delegator, and the waiter is the delegate. The customer delegates the task of taking the order and delivering it to the waiter. The waiter performs these tasks on behalf of the customer and then delegates the task of preparing the meal to the chef. Finally, the waiter serves the meal back to the customer.

Let’s take one more example, consider a Car class that needs to perform some operations related to engine management. Instead of implementing those operations directly in the Car class, we can delegate them to an Engine object. This way, the Car class can focus on its core functionality, while the Engine object handles engine-related tasks.

Delegation provides benefits such as modularity, maintainability, and flexibility in designing software systems.

Overview of the Delegation Pattern

The delegation pattern is a design pattern where an object delegates some or all of its responsibilities to another object. Instead of inheriting behavior, an object maintains a reference to another object and forwards method calls to it. This promotes composition over inheritance and provides greater flexibility in reusing and combining behaviors from different objects.

In Kotlin, the delegation pattern is built into the language, making it easy and convenient to implement. With the by keyword, Kotlin allows a class to implement an interface by delegating all of its public members to a specified object. Let’s dive into the details and see how it works.

Basic Usage of Delegation in Kotlin

To understand the basic usage of delegation in Kotlin, let’s consider a simple example. Assume we have an interface called Base with a single function print()

Kotlin
interface Base {
    fun print()
}

Next, we define a class BaseImpl that implements the Base interface. It has a constructor parameter x of type Int and provides an implementation for the print() function.

Kotlin
class BaseImpl(val x: Int) : Base {
    override fun print() {
        println(x)
    }
}

Now, we want to create a class called Derived that also implements the Base interface. Instead of implementing the print() function directly, we can delegate it to an instance of the Base interface. We achieve this by using the by keyword followed by the object reference in the class declaration.

Kotlin
class Derived(b: Base) : Base by b

In this example, the by clause in the class declaration indicates that b will be stored internally in objects of Derived, and the compiler will generate all the methods of Base that forward to b. This means that the print() function in Derived will be automatically delegated to the print() function of the b object.

To see the delegation in action, let’s create an instance of BaseImpl with a value of 10 and pass it to the Derived class. Then, we can call the print() function on the Derived object:

Kotlin
fun main() {
    val b = BaseImpl(10)
    val derived = Derived(b)
    derived.print() // Output: 10
}

When we execute the print() function on the Derived object, it internally delegates the call to the BaseImpl object (b), and thus it prints the value 10.

Overriding Methods in Delegation

In Kotlin, when a class implements an interface by delegation, it can also override methods provided by the delegate object. This allows for customization and adding additional behavior specific to the implementing class.

Let’s extend our previous example to understand method overriding in the delegation. Assume we have an interface Base with two functions: printMessage() and printMessageLine():

Kotlin
interface Base {
    fun printMessage()
    fun printMessageLine()
}

We modify the BaseImpl class to implement the updated Base interface with the two functions printMessage() and printMessageLine():

Kotlin
class BaseImpl(val x: Int) : Base {
    override fun printMessage() {
        println(x)
    }
    override fun printMessageLine() {
        println("$xn")
    }
}

Now, let’s update the Derived class to override the printMessage() function:

Kotlin
class Derived(b: Base) : Base by b {
    override fun printMessage() {
        println("softAai")
    }
}

In this example, the printMessage() function in the Derived class overrides the implementation provided by the delegate object b. When we call printMessage() on an instance of Derived, it will print “softAai” instead of the original implementation.

To test the overridden behavior, we can modify the main() function as follows:

Kotlin
fun main() {
    val b = BaseImpl(10)
    val derived = Derived(b)
    derived.printMessage() // Output: softAai
    derived.printMessageLine() // Output: 10\n
}

When we call the printMessage() function on the Derived object, it invokes the overridden implementation in the Derived class, and it prints “softAai” instead of 10. However, the printMessageLine() function is not overridden in the Derived class, so it delegates the call to the BaseImpl object, which prints the original value 10 followed by a new line.

Property Delegation

In addition to method delegation, Kotlin also supports property delegation. This allows a class to delegate the implementation of properties to another object. Let’s understand how it works.

Assume we have an interface Base with a read-only property message:

Kotlin
interface Base {
    val message: String
}

We modify the BaseImpl class to implement the Base interface with the message property:

Kotlin
class BaseImpl(val x: Int) : Base {
    override val message: String = "BaseImpl: x = $x"
}

Now, let’s update the Derived class to delegate the Base interface and override the message property:

Kotlin
class Derived(b: Base) : Base by b {
    override val message: String = "Message of Derived"
}

In this example, the Derived class delegates the implementation of the Base interface to the b object. However, it overrides the message property and provides its own implementation.

To see the property delegation in action, we can modify the main() function as follows:

Kotlin
fun main() {
    val b = BaseImpl(10)
    val derived = Derived(b)
    println(derived.message) // Output: Message of Derived
}

When we access the message property of the Derived object, it returns the overridden value “Message of Derived” instead of the one in the delegate object b.

Delegated Properties in Kotlin

Delegated properties allow you to delegate the implementation of property accessors (getters and setters) to another object. This means that instead of writing the logic for accessing and setting the property directly in the class, you can delegate it to a separate class.

The general syntax for creating a delegated property is as follows:

Kotlin
class MyClass {
    var myProperty: Type by Delegate()
}

Here, myProperty delegates its getter and setter operations to the Delegate object.

Property Delegates

Property delegates are classes that implement the getValue and optionally setValue functions. These functions are invoked when the delegated property is accessed or modified.

The getValue function is responsible for returning the property value, and setValue is responsible for updating the property value.

Let’s say we have a Delegate class that will handle the logic for accessing and setting a property. The Delegate class should have two methods: getValue() and setValue(). The getValue() method retrieves the current value of the property, and the setValue() method sets a new value for the property. These methods can be defined as either members or extensions of the Delegate class.

Here’s an example of a Delegate class that simply stores the value internally:

Kotlin
class Delegate {
    private var value: Int = 0

    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        // Get the current value of the property
        return value
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: Int) {
        // Set a new value for the property
        value = newValue
    }
}

In the above code snippet, the * is used in the parameter of the setValue() and getValue() methods of the Delegate class. This syntax is called a star-projection or star-spread operator.

In Kotlin, the KProperty interface represents a property, and it has a type parameter T that represents the type of property. When you use the star-projection (*) as the type argument (KProperty<*>), it means you are using a wildcard or an unknown type for the property.

In the context of delegated properties, the KProperty<*> parameter represents the property that is being accessed or set. The thisRef parameter represents the instance of the class that owns the property.

By using KProperty<*> with the star-projection, you’re saying that the property can be of any type. It allows you to create a generic Delegate class that can handle properties of different types without explicitly specifying the type.

Also, we defined the operator keyword, which is used to define and overload certain operators for custom types or classes.

Now, let’s create a class Foo with a property p that delegates its accessors to an instance of the Delegate class:

Kotlin
class Foo {
    var p: Int by Delegate()
    var q: String by Delegate()
}

fun main() {
    val foo = Foo()
    foo.p = 42
    foo.q = "softAai"

    println(foo.p) // Output: 42
    println(foo.q) // Output: softAai
}

When you create an instance of Foo, you can access and modify the p property as if it were a regular property. However, behind the scenes, the access and modification operations are delegated to the Delegate class.

Let’s consider one more example for better understanding, just create a simple UpperCaseDelegate that converts a string property to uppercase when accessed:

Kotlin
class UpperCaseDelegate {
    private var value: String = ""

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return value.toUpperCase()
    }
}

To use this delegate:

Kotlin
class MyClass {
    var myProperty: String by UpperCaseDelegate()
}

fun main() {
    val obj = MyClass()
    obj.myProperty = "resume sender app"
    println(obj.myProperty) // Output: RESUME SENDER APP
}

Here, myProperty delegates its getter operation to UpperCaseDelegate, which converts the value to uppercase before returning it.

The “by" keyword

The delegate object is specified after the by keyword and can be any object that satisfies the rules of the convention for property delegates. It’s the delegate object that actually handles the logic for accessing and setting the property.

The by keyword is used in Kotlin to indicate that a property is delegated to another object or class. When you use the by keyword, you are specifying that the implementation of the property’s accessors (getters and setters) will be delegated to a separate delegate object.

By using the by keyword, you make it clear that the property’s implementation is delegated to another object, enhancing code readability and allowing for better separation of concerns.

Kotlin
class Delegate {
    private var value: Int = 0

    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        // Get the current value of the property
        return value
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: Int) {
        // Set a new value for the property
        value = newValue
    }
}

class Foo {
    var p: Int by Delegate()
}

In the above example, the p property of the Foo class is delegated to an instance of the Delegate class using the by keyword. The Delegate class provides the implementation for the property’s accessors.

So, when you access or modify the p property of an instance of the Foo class, the property accessors are automatically delegated to the Delegate object. Behind the scenes, the getValue() and setValue() methods of the Delegate class are called to handle the property operations.

Using the by keyword simplifies the syntax and makes it clear that the property behavior is delegated to another object. It promotes code reuse and separates the concerns of the owning class from the delegate class.

Standard Delegates

Kotlin provides several standard delegates in the standard library to address common scenarios. Some examples include:

  • lazy: Allows for lazy initialization of properties. The initialization is deferred until the property is accessed for the first time.
  • observable: Enables observing property changes by providing a callback function that is triggered whenever the property value is modified.
  • vetoable: Allows validation of property values by providing a callback function that can reject value changes based on specific conditions.

Lazy Initialization

Lazy initialization is a technique where you delay the initialization of an object until it is accessed for the first time. This can be useful when the initialization process is resource-intensive and the object might not always be needed during the lifetime of the program.

For example, consider a Person class that lets you access a list of the emails written by a person. The emails are stored in a database and take a long time to access. You want to load the emails on first access to the property and do so only once. Let’s say you have the following function loadEmails, which retrieves the emails from the database:

Kotlin
class Email {
    /*...*/
}

fun loadEmails(person: Person): List<Email> {
    println("Load emails for ${person.name}")
    return listOf(/*...*/ )
}

Here’s how you can implement lazy loading using an additional _emails property that stores null before anything is loaded and the list of emails afterward.

Kotlin
class Person(val name: String) {
    private var _emails: List<Email>? = null

    val emails: List<Email>
        get() {
            if (_emails == null) {
                _emails = loadEmails(this)
            }
            return _emails!!
        }
}

In this implementation, we have a nullable _emails property that acts as a backing property to store the loaded emails. The emails property is the one we access to retrieve the list of emails. In the getter of the emails property, we check if _emails is null. If it is, we initialize it by calling the loadEmails function. We then return the value of _emails, forcibly unwrapping it with !! operator since we know it won’t be null at this point.

While this approach works, it can become cumbersome and error-prone when dealing with multiple lazy properties. Additionally, the implementation is not thread-safe.

To simplify and improve the code, Kotlin provides a built-in solution using the lazy delegate. The lazy function returns an object that has a getValue method, which can be used together with the by keyword to create a delegated property. Here’s how we can use it in our example:

Kotlin
class Person(val name: String) {
    val emails by lazy { loadEmails(this) }
}

With this implementation, the emails property is delegated to the lazy delegate. The lambda expression passed to the lazy function is used to initialize the value of the property when it is accessed for the first time. The lazy delegate ensures that the initialization happens only once, and subsequent accesses to the property will return the cached value.

The lazy function is thread-safe by default, meaning that the initialization is synchronized and can be safely accessed from multiple threads. If you need more control over the thread-safety or want to optimize for a single-threaded environment, you can specify additional options to the lazy function.

Lazy delegate: “by lazy()”

In Kotlin, you can achieve lazy initialization using the lazy delegate provided by the standard library. The lazy function returns an object that has a getValue method, which can be used together with the by keyword to create a delegated property.

The lazy delegate is a built-in feature in Kotlin that allows you to create properties whose values are computed lazily. It provides a concise way to implement lazy initialization without manually managing the initialization state. Here’s how you can use it:

Kotlin
val property: Type by lazy {
    // Initialization code here
    // This block will be executed only once, when the property is accessed for the first time
    // The value of the block will be cached and returned for subsequent accesses
    // Return the computed value
}

Here’s an example to illustrate the usage of lazy initialization with the lazy delegate:

Kotlin
class Example {
    val expensiveProperty: Int by lazy {
        // Expensive computation or initialization
        println("Initializing expensiveProperty...")
        // Return the computed value
        42
    }
}

fun main() {
    val example = Example()
    println("Before accessing expensiveProperty")
    // The initialization code of expensiveProperty is not executed yet
    println("Value of expensiveProperty: ${example.expensiveProperty}")
    // The initialization code of expensiveProperty is executed here
    println("After accessing expensiveProperty")
    println("Value of expensiveProperty: ${example.expensiveProperty}")
    // The cached value is returned without re-initialization
}
Kotlin
OUTPUT

Before accessing expensiveProperty
Initializing expensiveProperty...
Value of expensiveProperty: 42
After accessing expensiveProperty
Value of expensiveProperty: 42

In this example, the expensiveProperty in the Example class is lazily initialized using the lazy delegate. The initialization code block is not executed until the property is accessed for the first time. The computed value (42 in this case) is then cached and returned for subsequent accesses.

When you run the above code, you’ll see that the initialization code block is executed only once, when the property is first accessed. On subsequent accesses, the cached value is returned without re-executing the initialization code.

Lazy initialization with by lazy() simplifies the code by abstracting away the details of managing the initialization state and caching the computed value. It ensures that the property is initialized lazily and provides a convenient way to implement lazy initialization in Kotlin.

Once again here’s an example to illustrate lazy initialization using the lazy delegate:

Kotlin
class Person(val name: String) {
    val emails by lazy { loadEmails(this) }
}

In this example, the Person class has a property called emails, which is lazily initialized using the lazy delegate. The lazy function takes a lambda as an argument, which it will call to initialize the value of the property when it is accessed for the first time.

The benefit of using the lazy delegate is that the initialization logic is encapsulated within it. The value assigned to the emails property will only be computed once, on the first access, and subsequent accesses will return the cached value. This can help improve performance by avoiding unnecessary computations or resource allocations until they are actually needed.

You can think of the emails property as having a backing property that holds the computed value, and the lazy delegate takes care of initializing and caching the value behind the scenes. The delegate ensures that the value is computed lazily, i.e., only when it is first accessed.

Here’s how you would use the Person class:

Kotlin
val person = Person("amol")
println(person.emails) // Initialization happens here, loadEmails() is called
println(person.emails) // Cached value is returned without re-initialization

In this example, the loadEmails() function will only be called on the first access of the emails property. Subsequent accesses will return the cached value without re-initializing it.

The lazy delegate is thread-safe by default, meaning that the initialization is synchronized and can be safely accessed from multiple threads. However, if you know that the class will only be used in a single-threaded environment, you can provide additional options to bypass synchronization and improve performance.

The lazy delegate allows you to achieve lazy initialization of properties. It simplifies the code by encapsulating the initialization logic and ensures that the value is computed only when it is first accessed, providing better performance and resource utilization.

"observable” Delegate

The observable delegate allows you to observe property changes by providing a callback function that is triggered whenever the property value is modified.

Here’s the general syntax for using the observable delegate:

Kotlin
var propertyName: Type by Delegates.observable(initialValue) { property, oldValue, newValue ->
    // Callback function logic
}

Let’s see an example that uses the observable delegate to observe changes in a property:

Kotlin
import kotlin.properties.Delegates

class Person {
    var age: Int by Delegates.observable(25) { property, oldValue, newValue ->
        println("Age changed from $oldValue to $newValue")
    }
}

fun main() {
    val person = Person()
    person.age = 30 // Output: Age changed from 25 to 30
    person.age = 35 // Output: Age changed from 30 to 35
}

In this example, the age property is observed using the observable delegate. Whenever the age property is modified, the callback function is triggered, printing the old value and the new value.

"vetoable” Delegate

The vetoable delegate allows you to validate property values by providing a callback function that can reject value changes based on specific conditions.

Here’s the general syntax for using the vetoable delegate:

Kotlin
var propertyName: Type by Delegates.vetoable(initialValue) { property, oldValue, newValue ->
    // Validation logic
    // Return true to accept the new value, or false to reject it
}

Let’s see an example that uses the vetoable delegate to validate a property value:

Kotlin
import kotlin.properties.Delegates

class Circle {
    var radius: Double by Delegates.vetoable(0.0) { property, oldValue, newValue ->
        newValue >= 0.0 // Only accept positive or zero radius values
    }
}

fun main() {
    val circle = Circle()
    circle.radius = 5.0
    println(circle.radius) // Output: 5.0

    circle.radius = -2.0 // Value rejected due to validation
    println(circle.radius) // Output: 5.0 (unchanged)
}

In this example, the radius property is validated using the vetoable delegate. The callback function checks if the new value is greater than or equal to zero. If the validation condition is not met (e.g., negative radius), the value change is rejected, and the property retains its previous value.

Delegating to another property

Delegating a property to another property means that the getter and setter of one property are implemented by accessing or modifying another property’s value. This delegation can be done for top-level properties, member properties (including extension properties) within the same class, or even member properties of another class.

To delegate a property to another property, you use the :: qualifier followed by the delegate property’s name. Here are a few examples to illustrate how property delegation works:

Kotlin
var topLevelInt: Int = 0

class ClassWithDelegate(val anotherClassInt: Int)

class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) {
    var delegatedToMember: Int by this::memberInt
    var delegatedToTopLevel: Int by ::topLevelInt

    val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}

var MyClass.extDelegated: Int by ::topLevelInt

In the code above, we have different scenarios for property delegation:

  1. delegatedToMember is a property within the MyClass class that delegates its getter and setter to the memberInt property of the same class. This means that accessing or modifying delegatedToMember will actually read from or write to memberInt.
  2. delegatedToTopLevel is a property within the MyClass class that delegates its getter and setter to the top-level property topLevelInt. So, accessing or modifying delegatedToTopLevel will actually read from or write to topLevelInt.
  3. delegatedToAnotherClass is a property within the MyClass class that delegates its getter to the anotherClassInt property of an instance of ClassWithDelegate. This means that accessing delegatedToAnotherClass will read the value of anotherClassInstance.anotherClassInt.
  4. extDelegated is an extension property of MyClass that delegates its getter and setter to the top-level property topLevelInt. This allows instances of MyClass to have an additional property extDelegated that shares its value with topLevelInt.

Property delegation can be useful in various scenarios. One common use case is when you want to introduce a new property while maintaining backward compatibility with an existing one. In such cases, you can introduce a new property, annotate the old property with the @Deprecated annotation, and delegate its implementation to the new property. Here’s an example:

Kotlin
class MyClass {
    var newName: Int = 0

    @Deprecated("Use 'newName' instead", ReplaceWith("newName"))
    var oldName: Int by this::newName
}

fun main() {
    val myClass = MyClass()
    // Notification: 'oldName: Int' is deprecated.
    // Use 'newName' instead
    myClass.oldName = 42
    println(myClass.newName) // Output: 42
}

In this example, we have a class MyClass with oldName and newName properties. The oldName property is deprecated and annotated with @Deprecated, indicating that it should not be used anymore. The implementation of oldName is delegated to the newName property using by this::newName. So, accessing or modifying oldName will actually access or modify the newName property.

In the main function, we demonstrate the usage of the deprecated oldName property. When assigning a value to oldName, a deprecation warning is displayed. However, the value is stored in the newName property, which can be accessed correctly.

Overall, delegating properties to other properties provides a powerful mechanism to reuse existing property implementations, introduce backward compatibility, and simplify property access and modification.

Property delegate requirements

Property delegate requirements will be demonstrated for both read-only (val) and mutable (var) properties. Let’s break down the concept of delegated properties for read-only and mutable properties (var) and understand how to provide the necessary operator functions for delegation.

For a read-only property (val), the delegate must provide the getValue() operator function with the following parameters:

  • thisRef: This parameter should be the same type as, or a supertype of, the property owner (for extension properties, it should be the type being extended).
  • property: This parameter should be of type KProperty<*> or its supertype.
  • getValue(): This function must return the same type as the property (or its subtype).

Here’s an example:

Kotlin
class Resource

class Owner {
    val valResource: Resource by ResourceDelegate()
}

class ResourceDelegate {
    operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource {
        return Resource()
    }
}

In this code, we have the Owner class with a read-only property valResource. The delegation is done by using the by keyword and providing an instance of the ResourceDelegate class. The ResourceDelegate class defines the getValue() function, which returns an instance of Resource. The function receives the property owner (thisRef) and the KProperty<*> instance representing the property being delegated.

For a mutable property (var), in addition to the getValue() function, the delegate must provide the setValue() operator function with the following parameters:

  • thisRef: This parameter should be the same type as, or a supertype of, the property owner (for extension properties, it should be the type being extended).
  • property: This parameter should be of type KProperty<*> or its supertype.
  • value: This parameter should be of the same type as the property (or its supertype).

Here’s an example:

Kotlin
class Resource

class Owner {
    var varResource: Resource by ResourceDelegate()
}

class ResourceDelegate(private var resource: Resource = Resource()) {
    operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource {
        return resource
    }

    operator fun setValue(thisRef: Owner, property: KProperty<*>, value: Any?) {
        if (value is Resource) {
            resource = value
        }
    }
}

In this code, we have the Owner class with a mutable property varResource. The ResourceDelegate class now includes the setValue() function, which allows modifying the value of the delegated property. The function receives the property owner (thisRef), the KProperty<*> instance representing the property being delegated, and the new value to be assigned.

You can define the getValue() and setValue() functions as member functions of the delegate class itself or as extension functions. Both functions need to be marked with the operator keyword to enable operator overloading.

Alternatively, you can create delegates as anonymous objects using the interfaces ReadOnlyProperty and ReadWriteProperty from the Kotlin standard library. These interfaces provide the required getValue() and setValue() methods. By using anonymous objects, you can avoid creating separate classes for the delegates. Here’s an example:

Kotlin
fun resourceDelegate(resource: Resource = Resource()): ReadWriteProperty<Any?, Resource> =
    object : ReadWriteProperty<Any?, Resource> {
        private var curValue = resource

        override fun getValue(thisRef: Any?, property: KProperty<*>): Resource = curValue

        override fun setValue(thisRef: Any?, property: KProperty<*>, value: Resource) {
            curValue = value
        }
    }

val readOnlyResource: Resource by resourceDelegate()  // ReadWriteProperty used as a read-only property
var readWriteResource: Resource by resourceDelegate()  // ReadWriteProperty used as a mutable property

In this code, the resourceDelegate() function returns an anonymous object implementing the ReadWriteProperty interface. The ReadWriteProperty interface extends ReadOnlyProperty, so it can be used as a delegate for both read-only and mutable properties. The anonymous object defines the necessary getValue() and setValue() functions.

By using delegated properties and providing the appropriate operator functions, you can create flexible and reusable property delegation patterns in Kotlin.

Storing property values in a map

Another common pattern where delegated properties come into play is objects that have a dynamically defined set of attributes associated with them. Such objects are sometimes called expando objects. in a contact-management system, each person may have some required properties (like name) that are handled in a special way, as well as additional attributes that can vary for each person(youngest child’s birthday, for example).

One way to implement such a system is by using a map to store all the attributes of a person and providing properties that allow access to the information with special handling. Let’s go through the code examples to understand this approach.

First, we have the Person class with a private _attributes map. This map will store the attributes of a person, where the keys are attribute names and the values are attribute values.

Kotlin
class Person {
    private val _attributes = hashMapOf<String, String>()

    fun setAttribute(attrName: String, value: String) {
        _attributes[attrName] = value
    }

    val name: String
        get() = _attributes["name"]!!
}

In this code, we have a set attributefunction that allows adding or updating attributes in the _attributes map. The name property is an example of a required property that is handled in a special way. It retrieves the value of the “name” attribute from the _attributes map.

To create an instance of the Person class and load data into it, we can use a generic API, such as deserialization from JSON, as shown in the example below:

Kotlin
val p = Person()
val data = mapOf("name" to "amol", "company" to "softAai")

for ((attrName, value) in data) {
    p.setAttribute(attrName, value)
}

println(p.name) // Output: amol

Here, we create a new Person instance and provide the data as a map. We iterate over each key-value pair in the data map and call setAttribute to store the attributes in the _attributes map. Finally, we can access the name property, which internally retrieves the value of the “name” attribute from the _attributes map.

Now, instead of manually implementing the property and the _attributes map, we can simplify the code using delegated properties. We can directly delegate the name property to the _attributes map using the by keyword, as shown below:

Kotlin
class Person {
    private val _attributes = hashMapOf<String, String>()

    fun setAttribute(attrName: String, value: String) {
        _attributes[attrName] = value
    

    val name: String by _attributes
}

In this code, we no longer have the explicit getter for the name property. Instead, we use the by keyword to delegate the property to the _attributes map. The standard library provides getValue and setValue extension functions for maps, allowing the property to automatically get and set the values in the map based on the property name.

With the delegated property in place, we can use it just like before:

Kotlin
val p = Person()
val data = mapOf("name" to "amol", "company" to "softAai")

for ((attrName, value) in data) {
    p.setAttribute(attrName, value)
}

println(p.name) // Output: amol

The output remains the same, but now the name property is implemented as a delegated property, simplifying the code and removing the need for an explicit getter.

Delegated properties provide a concise and reusable way to handle dynamically defined attributes in expando objects. By leveraging the by keyword and the standard library extension functions, we can delegate the property access to a map or any other custom logic, making the code more maintainable and flexible.

Translation rules for delegated properties

When using delegated properties in Kotlin, the Kotlin compiler generates auxiliary properties to handle the delegation. These auxiliary properties are used to store the delegate object and manage the getter and setter operations.

Let’s take an example to understand how this works. Consider the following code:

Kotlin
class C {
    var prop: Type by MyDelegate()
}

When the compiler encounters this code, it generates a hidden property called prop$delegate. This hidden property is of the same type as the delegate class (MyDelegate in this case). It is responsible for handling the delegation of the prop property.

The generated code looks like this:

Kotlin
class C {
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

In the generated code, the prop property has a getter and a setter. The getter delegates the getValue() operation to the prop$delegate property, passing the instance of the outer class (this) and the reflection object (this::prop) that represents the property itself. The delegate’s getValue() function is responsible for providing the value of the property.

Similarly, the setter delegates the setValue() operation to the prop$delegate property, passing the instance of the outer class (this), the reflection object (this::prop), and the new value of the property. The delegate’s setValue() function handles the assignment of the new value.

By generating the prop$delegate property and delegating to it, the compiler ensures that the getter and setter operations are correctly handled by the delegate object (MyDelegate).

Optimized cases for delegated properties

When it comes to optimization, the Kotlin compiler can omit the $delegate field in certain cases. Here are the optimized cases for delegated properties:

A referenced property:

Kotlin
class C<Type> {
    private var impl: Type = ...
    var prop: Type by ::impl
}

In this case, the property prop is delegated to another property impl using the by keyword. Since the delegate is a referenced property within the same class, the compiler can optimize the generated code and omit the $delegate field. Instead, the accessors directly delegate to the referenced property impl.

A named object:

Kotlin
object NamedObject {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String = ...
}

val s: String by NamedObject

When using a named object as a delegate, the Kotlin compiler can optimize the code and omit the $delegate field. The accessors directly call the delegate’s getValue function without the need for an intermediate property.

A final val property with a backing field and a default getter in the same module:

Kotlin
val impl: ReadOnlyProperty<Any?, String> = ...

class A {
    val s: String by impl
}

In this case, the delegate impl is a final val property with a backing field and a default getter defined in the same module as the property s. The compiler can optimize the code and omit the $delegate field. The accessors directly delegate to the getValue function of the delegate without the need for an intermediate property.

A constant expression, enum entry, this, or null:

Kotlin
class A {
    operator fun getValue(thisRef: Any?, property: KProperty<*>) ...

    val s by this
}

If the delegate is a constant expression, an enum entry, this, or null, the Kotlin compiler can optimize the code and omit the $delegate field. The accessors directly call the getValue function of the delegate without the need for an intermediate property.

In these optimized cases, the compiler eliminates the need for the $delegate field, which can save memory and provide more efficient property access. The accessors directly invoke the corresponding functions of the delegate, leading to more streamlined code execution.

Note that these optimizations are applied by the Kotlin compiler to improve performance and reduce unnecessary overhead when using delegated properties.

Translation rules when delegating to another property

When delegating to another property, the Kotlin compiler optimizes the code by generating immediate access to the referenced property. This means that the compiler doesn’t generate the $delegate field. This optimization helps save memory and improves performance.

Let’s take a look at the example code:

Kotlin
class C<Type> {
    private var impl: Type = ...
    var prop: Type by ::impl
}

In this case, the property prop is delegated to the property impl using the by keyword. The compiler optimizes the code by directly accessing the impl property within the property accessors of prop. This means that the delegated property’s getValue and setValue operators are skipped, and there is no need for the KProperty reference object.

The compiler generates the following code:

Kotlin
class C<Type> {
    private var impl: Type = ...

    var prop: Type
        get() = impl
        set(value) {
            impl = value
        }

    fun getProp$delegate(): Type = impl // This method is needed only for reflection

As you can see, the accessors for the prop property directly delegate to the impl property. The getValue accessor returns the value of impl, and the setValue accessor assigns the value to impl.

The method getProp$delegate() is also generated, but it is only needed for reflection purposes. It allows reflective access to the delegate object, but it is not used in regular property access.

This optimization avoids the creation of an additional field and reduces the overhead associated with delegated property access. By directly accessing the referenced property, the code becomes more efficient and memory-friendly.

The same optimization principle applies when delegating to another property using this keyword. The compiler generates immediate access to the referenced property without the need for an intermediate field.

Overall, these translation rules improve the performance of delegated properties and eliminate unnecessary memory usage.

What will be the right-hand side of “by"?

In Kotlin, when using delegated properties, the expression to the right of the by keyword can be more than just a new instance creation. It can also be a function call, another property, or any other expression, as long as the value of the expression is an object that provides the getValue and setValue methods with the correct parameter types.

The getValue and setValue methods can be declared directly on the object itself or defined as extension functions. This gives you the flexibility to use existing functions or properties to handle the behavior of your delegated properties.

Here’s an example to demonstrate this concept:

Kotlin
class Example {
    var value: String = "initial"

    // Delegated property using a function call
    var customValue: String by getValueFromFunction()

    // Delegated property using another property
    var anotherValue: String by ::value

    // Delegated property using an extension function
    var computedValue: Int by calculateValue()

    // Extension function providing delegated property behavior
    private fun calculateValue(): ReadWriteProperty<Any?, Int> {
        var storedValue: Int = 0

        return object : ReadWriteProperty<Any?, Int> {
            override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
                // Perform custom logic to compute the value
                return storedValue * 2
            }

            override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
                // Perform custom logic to store the value
                storedValue = value / 2
            }
        }
    }
}

fun getValueFromFunction(): ReadWriteProperty<Any?, String> {
    var storedValue: String = ""

    return object : ReadWriteProperty<Any?, String> {
        override fun getValue(thisRef: Any?, property: KProperty<*>): String {
            return storedValue
        }

        override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
            storedValue = value.toUpperCase()
        }
    }
}

fun main() {
    val example = Example()

    // Using the delegated properties
    example.customValue = "amol"
    println(example.customValue) // Output: AMOL

    example.anotherValue = "softAai"
    println(example.anotherValue) // Output: softAai

    example.computedValue = 5
    println(example.computedValue) // Output: 10
}

In the example above, the customValue property is delegated to the result of a function call getValueFromFunction(), which returns a custom delegate object implementing the ReadWriteProperty interface. The delegate modifies the stored value by converting it to uppercase when setting the value.

The anotherValue property is delegated to the value property using the ::value syntax. Any changes made to anotherValue will be reflected in the value property.

The computedValue property is delegated to the result of the calculateValue() extension function. The extension function provides the delegated property behavior by implementing the ReadWriteProperty interface. In this case, the delegate computes the value by multiplying it by 2 and stores the value by dividing it by 2.

By allowing various expressions on the right-hand side of by and supporting both object-defined and extension-defined getValue and setValue methods, Kotlin enables flexible and customizable behavior for delegated properties.

Providing a delegate

The provideDelegate operator allows you to extend the logic for creating the object to which the property implementation is delegated. If the object used on the right-hand side of the by keyword defines provideDelegate as a member or extension function, that function will be called to create the property delegate instance.

One use case of provideDelegate is to perform additional checks or actions during the initialization of the property delegate. For example, you can check the consistency of the property before binding it.

Here’s an example that demonstrates how to use provideDelegate:

Kotlin
class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> {
    override fun getValue(thisRef: MyUI, property: KProperty<*>): T { ... }
}

class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty<*>
    ): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        // create delegate
        return ResourceDelegate()
    }

    private fun checkProperty(thisRef: MyUI, name: String) { ... }
}

class MyUI {
    fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... }

    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}

In this example, the ResourceLoader class defines the provideDelegate function. This function is called for each property during the creation of an instance of MyUI. It performs the necessary validation or checks before creating the property delegate.

Without the provideDelegate functionality, you would need to pass the property name explicitly to achieve the same functionality, which could be less convenient.

The provideDelegate method has the same parameters as the getValue function:

  • thisRef must be the same type as, or a supertype of, the property owner (for extension properties, it should be the type being extended).
  • property must be of type KProperty<*> or its supertype.

The provideDelegate method is responsible for creating and returning the property delegate instance that will handle the property access.

In the generated code, when provideDelegate is present, it is called to initialize the auxiliary prop$delegate property. Compare the generated code for the property declaration val prop: Type by MyDelegate() with the generated code when provideDelegate is available:

Kotlin
class C {
    var prop: Type by MyDelegate()
}

// Generated code with `provideDelegate` function:
class C {
    // Calling `provideDelegate` to create the additional `delegate` property
    private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

It’s important to note that the provideDelegate method only affects the creation of the auxiliary property (prop$delegate) and does not impact the generated code for the getter or the setter of the delegated property.

You can also use the PropertyDelegateProvider interface from the standard library to create delegate providers without creating new classes. Here’s an example:

Kotlin
val provider = PropertyDelegateProvider { thisRef: Any?, property ->
    ReadOnlyProperty<Any?, Int> { _, property -> 42 }
}
val delegate: Int by provider

In this case, the PropertyDelegateProvider creates a delegate provider using a lambda expression. The lambda receives the thisRef (property owner) and property information and returns a property delegate instance.

Delegation in Android Applications

Kotlin delegation and delegated properties can be useful in Android applications to simplify code, separation of concerns, and provide flexibility in handling certain tasks. Here are a few examples of how delegation can be used in Android applications:

1. Shared Preferences Delegation

Android applications often need to store and retrieve key-value pairs using SharedPreferences. Delegation can be used to simplify the code for accessing SharedPreferences. For example:

Kotlin
class SettingsManager(context: Context) {
    private val preferences: SharedPreferences by lazy {
        context.getSharedPreferences("settings", Context.MODE_PRIVATE)
    }

    var isNotificationsEnabled: Boolean by BooleanPreferenceDelegate(
        preferences, "notifications_enabled", true
    )
}

class BooleanPreferenceDelegate(
    private val preferences: SharedPreferences,
    private val key: String,
    private val defaultValue: Boolean
) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
        return preferences.getBoolean(key, defaultValue)
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
        preferences.edit { putBoolean(key, value) }
    }
}

In this example, the SettingsManager class uses delegation to handle the isNotificationsEnabled property, which is backed by a shared preference value. The BooleanPreferenceDelegate class implements the delegated property behavior.

2. Dependency Injection with Delegation:

Dependency injection frameworks like Dagger can benefit from delegation to simplify the injection process. By using a delegated property, you can abstract away the complexity of dependency resolution. Here’s a simplified example:

Kotlin
class MyActivity : AppCompatActivity() {
    private val myDependency: MyDependency by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Use myDependency
    }
}

interface Injectable {
    fun injectDependencies()
}

class MyDependency {
    // Dependency implementation
}

inline fun <reified T : Injectable> AppCompatActivity.inject(): Lazy<T> {
    return lazy {
        val injectable = T::class.java.newInstance()
        injectable.injectDependencies()
        injectable
    }
}

In this example, the MyActivity class uses delegation to lazily inject the MyDependency instance. The inject function provides the delegation logic for dependency injection, making it easy to reuse across different activities.

These are just a few examples of how delegation and delegated properties can be used in Android applications. They demonstrate how delegation can simplify code, improve code organization, and provide flexibility in various scenarios.

Conclusion

Kotlin delegation and delegated properties provide an elegant and efficient way to handle code reuse and separation of concerns. By understanding the concept of delegation and utilizing delegated properties, you can write cleaner, more maintainable code in your Kotlin projects. Whether you’re developing Android applications or working on other Kotlin projects, delegation is a powerful tool to enhance your codebase. Start exploring the possibilities of delegation in Kotlin and unlock the benefits it brings to your software development journey.

Skill Up: Software & AI Updates!

Receive our latest insights and updates directly to your inbox

Related Posts

error: Content is protected !!