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
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")
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
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
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"
viewModels() as Delegation
class MyFragment : Fragment() {
private val viewModel: MyViewModel by viewModels()
}
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)