Property Delegation in Kotlin (lazy, observable, vetoable)

Property Delegation in Kotlin is a feature that allows a property to delegate its getter and setter logic to another object. This mechanism is achieved using the by keyword and helps in reducing boilerplate code, improving reusability, and adding flexibility to how properties behave.

1. What is Property Delegation?

Normally, when you define a property in a class, you manage its value storage and retrieval directly. However, with delegated properties, you assign these responsibilities to a delegate object, which handles them for you.


class Example {
    var name: String by Delegate()
}
  

Here, the class Delegate defines how the property name is read and written.

2. Types of Property Delegation

2.1 Lazy Delegation

The lazy delegate is used when you want to initialize a property only once and only when it is first accessed (also known as lazy initialization). This is especially useful for expensive computations or objects that might not always be needed.


class UserProfile {
    val userData: String by lazy {
        println("Loading user data...")
        "User: Talha"
    }
}

fun main() {
    val profile = UserProfile()
    println(profile.userData)  // Initializes and prints the value
    println(profile.userData)  // Uses cached value, does not reinitialize
}
  

Output:


Loading user data...
User: Talha
User: Talha
  

2.2 Observable Delegation

The observable delegate is used when you want to monitor changes to a property. It takes an initial value and a lambda that is called every time the property changes.


import kotlin.properties.Delegates

class Settings {
    var theme: String by Delegates.observable("Light") { property, oldValue, newValue ->
        println("Theme changed from $oldValue to $newValue")
    }
}

fun main() {
    val settings = Settings()
    settings.theme = "Dark"
    settings.theme = "Blue"
}
  

Output:


Theme changed from Light to Dark
Theme changed from Dark to Blue
  

2.3 Vetoable Delegation

The vetoable delegate works similarly to observable, but it allows you to decide whether to accept or reject a new value before it is assigned.


import kotlin.properties.Delegates

class BankAccount {
    var balance: Int by Delegates.vetoable(0) { _, oldValue, newValue ->
        if (newValue >= 0) {
            println("Balance updated from $oldValue to $newValue")
            true   // Accept new value
        } else {
            println("Invalid balance! Cannot assign negative value.")
            false  // Reject new value
        }
    }
}

fun main() {
    val account = BankAccount()
    account.balance = 500
    account.balance = -100
}
  

Output:


Balance updated from 0 to 500
Invalid balance! Cannot assign negative value.
  

3. Custom Delegates

You can also create your own custom delegate by defining an object with getValue and setValue operator functions.


import kotlin.reflect.KProperty

class StringDelegate {
    private var storedValue: String = "Default"

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

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

class Example {
    var text: String by StringDelegate()
}

fun main() {
    val ex = Example()
    ex.text = "Hello Kotlin"
    println(ex.text)
}
  

4. Key Differences Between Lazy, Observable, and Vetoable

Delegate Type Purpose When Executed Example Use Case
lazy Initializes a property only once, on first access. At first access. Loading user profile data, configuration, or heavy objects.
observable Observes property value changes. Every time value changes. Updating UI or preferences.
vetoable Validates and decides whether to accept a new value. Before value assignment. Input validation or enforcing constraints.

5. Summary

In essence: Property Delegation in Kotlin enhances flexibility, reduces boilerplate code, and provides elegant ways to manage state, validation, and initialization.