Property Delegation Kotlin

Mercy Jemosop
3 min readMar 5, 2024

Introduction

Welcome to another learning session, today we are covering property delegation in kotlin. To break down to topic lets understand the terminologies used in out title.

Properties are ussually backed directly by corresponding fields. Normally, properties have associated fields directly backing them, however this isn’t always the case; properties can still exist as long as they are appropriately exposed to the outside world.

What’s a delegate class ?
This is a class that provides the property of a value and handles its changes. It will help us to assign or pass on(delegate), the getter-setter logic altogether to a different class so that it can help us in reusing the code.
This is an alternative for inheritance property.

Examples of built in delegate property

  • Lazy properties- value gets computed only upon first access
  • Observable properties- listeners get notified about changes to this property.
  • Storing properties in a map instead of a separate field for each property.

Lazy properties

Lazy() takes a lambda and returns an instance of Lazy<T>, which can serve as a delegate for implementing lazy property.

The first call to get() executes the lambda passed to get lazy() and remembers the result. Subsequent calls to get() simply returns the remembered results.

The evolution of lazy properties is synchronized by default meaning the value is computed only in one thread but all other threads will see the same value.

In situations where synchronization of the initialization delegate is not required to allow multiple threads to execute simultaniously. We need to pass LazyThreadSafetyMode.PUBLICATION as a parameter to lazy().

If you're sure that the initialization will always happen in the same thread as the one where you use the property, you can use LazyThreadSafetyMode.NONE. It doesn't incur any thread-safety guarantees and related overhead.

val lazyValue: String by lazy {
println("computed!")
"Hello"
}

fun main() {
println(lazyValue)
println(lazyValue)
}
//output
computed!
Hello
Hello

Observable properties

Delegate.observable() takes two arguments

  • The initial value
  • A handler for modifications

The handler is called everytime you assign a property. It has three parameters.

  • The property being assigned to
  • The old value
  • The new value
import kotlin.properties.Delegates

class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}

fun main() {
val user = User()
user.name = "first"
user.name = "second"
}

/// output
<no name> -> first
first -> second

If you want to intercept assignments and veto them, use vetoable() instead of observable(). The handler passed to vetoable will be called before the assignment of a new property value.


var max: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
newValue > oldValue
}

fun main() {
println(max) // 0

max = 10
println(max) // 10

max = 5
println(max) // 10
}

/// output
0
10
10

Storing properties in a map

This is useful in applications for things like: parsing Json or performing other dynamic tasks.
You can use the map instance itself as the delegate for a delegated property


class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
fun main() {
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25
}

/// output
John Doe
25

Delegating to another property

A property can delegate its getter and setter to another property. This delegation is available in both top-level and class properties(member and extension).

Delegate property can be:

  • A top-level property
  • A member or an extension property of the same class
  • A member or an extension property of another class

We use : : qualifier in the delegate name to delegate to another property

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

Usage:

  • When you want to rename a property in a backward compatible way. introduce a new property, annotate the old one with the @Deprecated annotation, and delegate its implementation.
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) // 42
}
/// output
42

That’s the end. To learn more visit the official documentation

--

--

Mercy Jemosop

Software Developer. I am open to job referrals. connect with me on twitter @kipyegon_mercy