kotlinadvanced
Custom Property Delegates
Create reusable property delegates: validation, logging, caching, and thread-safe lazy initialization.
kotlinPress ⌘/Ctrl + Shift + C to copy
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
// Validated delegate
class Validated<T>(
private var value: T,
private val validator: (T) -> Boolean,
private val errorMessage: (T) -> String = { "Invalid value: $it" }
) : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T = value
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
require(validator(value)) { errorMessage(value) }
this.value = value
}
}
fun <T> validated(initial: T, message: String = "", validator: (T) -> Boolean) =
Validated(initial, validator) { if (message.isNotEmpty()) message else "Invalid: $it" }
// Logging delegate
class LoggingDelegate<T>(private var value: T) : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
println("[GET] ${property.name} = $value")
return value
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
println("[SET] ${property.name}: ${this.value} → $value")
this.value = value
}
}
fun <T> logged(initial: T) = LoggingDelegate(initial)
// Expiring cache delegate
class ExpiringCache<T>(
private val ttlMs: Long,
private val loader: () -> T
) : ReadWriteProperty<Any?, T> {
private var cachedValue: T? = null
private var lastLoad: Long = 0
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val now = System.currentTimeMillis()
if (cachedValue == null || now - lastLoad > ttlMs) {
cachedValue = loader()
lastLoad = now
}
return cachedValue!!
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
cachedValue = value
lastLoad = System.currentTimeMillis()
}
}
fun <T> cached(ttlMs: Long, loader: () -> T) = ExpiringCache(ttlMs, loader)
// Observable with veto power
class VetoableObservable<T>(
private var value: T,
private val onChange: (old: T, new: T) -> Boolean
) : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>) = value
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
if (onChange(this.value, value)) {
this.value = value
}
}
}
// Trimmed string delegate
object TrimmedString {
operator fun provideDelegate(
thisRef: Any?,
property: KProperty<*>
): ReadWriteProperty<Any?, String> {
return object : ReadWriteProperty<Any?, String> {
private var value = ""
override fun getValue(thisRef: Any?, property: KProperty<*>) = value
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
this.value = value.trim()
}
}
}
}
// Usage
class UserProfile {
var name: String by logged("")
var age: Int by validated(0, "Age must be 0-150") { it in 0..150 }
var email: String by validated("") { it.contains("@") || it.isEmpty() }
var bio: String by TrimmedString
// Built-in delegates
val greeting: String by lazy { "Hello, $name!" }
val properties: MutableMap<String, Any?> = mutableMapOf()
var nickname: String by properties.withDefault { "" }
}
fun main() {
val profile = UserProfile()
// Logging delegate
profile.name = "Alice"
println("Name is: ${profile.name}")
// Validated
profile.age = 30
println("Age: ${profile.age}")
try { profile.age = 200 } catch (e: Exception) { println("Error: ${e.message}") }
// Trimmed
profile.bio = " Hello World! "
println("Bio: '${profile.bio}'")
// Lazy
println(profile.greeting)
// Map-backed
profile.nickname = "Ali"
println("Nickname: ${profile.nickname}")
println("Properties map: ${profile.properties}")
// Caching delegate
var config: Map<String, String> by cached(5000) {
println("Loading config...")
mapOf("key" to "value-${System.currentTimeMillis()}")
}
println(config)
println(config) // cached, no reload
}Use Cases
- Input validation on property assignment
- Debug logging for state changes
- Cached values with TTL expiration
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
kotlinintermediate
Extension Properties and Receiver Functions
Add properties and functions to existing classes: extension receivers, generic extensions, and DSL patterns.
Best for: Adding utility methods to existing types
#kotlin#extensions
kotlinintermediate
Delegation — by, lazy, observable, and Custom
Use Kotlin delegation for reusable behavior: by keyword, lazy, observable, vetoable, and map-backed.
Best for: Composing behavior without deep inheritance
#kotlin#delegation
kotlinbeginner
Enum Classes — Advanced Patterns
Use Kotlin enum classes with properties, methods, interfaces, and companion utilities.
Best for: Type-safe constant sets with behavior
#kotlin#enum
kotlinintermediate
Interface Delegation with 'by'
Delegate interface implementations with the 'by' keyword: composition over inheritance patterns.
Best for: Composition over inheritance
#kotlin#delegation