DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Kotlin Delegation Patterns — by, lazy, observable & Custom Delegates

Kotlin Delegation Patterns — by, lazy, observable & Custom Delegates

Delegation is one of Kotlin's most powerful features. It reduces boilerplate and encourages composition over inheritance.

Class Delegation with 'by'

interface Logger {
    fun log(message: String)
}

class ConsoleLogger : Logger {
    override fun log(message: String) = println(message)
}

class Application(logger: Logger) : Logger by logger

// Usage
val app = Application(ConsoleLogger())
app.log("App started")  // Delegates to ConsoleLogger
Enter fullscreen mode Exit fullscreen mode

The interface methods are automatically forwarded to the delegated object.

Lazy Initialization

val database: Database by lazy {
    println("Initializing database...")
    Database()
}

// First access initializes
database.query("SELECT * FROM users")
Enter fullscreen mode Exit fullscreen mode

Perfect for expensive operations that may never be needed.

Observable & Vetoable Properties

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

var salary: Int by Delegates.vetoable(50000) { prop, oldValue, newValue ->
    newValue > oldValue  // Reject decreases
}

age = 25  // Prints: Age changed from 0 to 25
salary = 40000  // Rejected
salary = 60000  // Accepted
Enter fullscreen mode Exit fullscreen mode

Map Delegation

val person = mutableMapOf(
    "name" to "Alice",
    "age" to 30,
    "city" to "New York"
)

val name: String by person
val age: Int by person

println("$name is $age years old")  // Alice is 30 years old
Enter fullscreen mode Exit fullscreen mode

Useful for dynamic property access.

Custom Delegate Implementation

SharedPreferences example:

class SharedPrefsDelegate<T>(
    private val key: String,
    private val defaultValue: T,
    private val prefs: SharedPreferences
) : ReadWriteProperty<Any?, T> {

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return when (defaultValue) {
            is String -> prefs.getString(key, defaultValue) as T
            is Int -> prefs.getInt(key, defaultValue) as T
            is Boolean -> prefs.getBoolean(key, defaultValue) as T
            else -> defaultValue
        }
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        prefs.edit().apply {
            when (value) {
                is String -> putString(key, value)
                is Int -> putInt(key, value)
                is Boolean -> putBoolean(key, value)
            }
        }.apply()
    }
}

// Usage
class SettingsManager(prefs: SharedPreferences) {
    var theme: String by SharedPrefsDelegate("theme", "dark", prefs)
    var notifications: Boolean by SharedPrefsDelegate("notifications", true, prefs)
}

val settings = SettingsManager(getSharedPreferences("app", MODE_PRIVATE))
settings.theme = "light"  // Persists immediately
println(settings.theme)  // "light"
Enter fullscreen mode Exit fullscreen mode

viewModels() as Delegation

class MyFragment : Fragment() {
    private val viewModel: MyViewModel by viewModels()
}
Enter fullscreen mode Exit fullscreen mode

Behind the scenes, viewModels() is a property delegate using ReadOnlyProperty.

When to Use Each

Pattern Use Case
Class by Composition over inheritance
lazy Expensive one-time initialization
observable Track state changes
vetoable Validate before assignment
Custom Domain-specific persistence/behavior

Delegation makes code cleaner, testable, and maintainable. Use it liberally.


Interested in mobile app development? Check out 8 Android app templates on Gumroad!

Top comments (0)