This page looks best with JavaScript enabled

Kotlin Delegates

 ·  ☕ 4 min read  ·  ✍️ Ignacio Carrión

✨ Understanding Kotlin Delegates: The Magic Behind Cleaner Code ✨

Kotlin delegates are a powerful feature that lets you delegate the behavior of a property or even an interface implementation to another object. Instead of writing repetitive logic or managing state directly, you can delegate this responsibility to reusable and specialized classes.


How Delegates Work

Delegates in Kotlin work by using the by keyword, which redirects the behavior of a property or an interface to a delegate object. For properties, the delegate object provides custom implementations for the get and/or set methods. For interface delegation, the class implementation is forwarded to the provided delegate.

Here’s an example of property delegation using a custom delegate:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class StringDelegate {  
    private var value: String = ""  

    operator fun getValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>): String {  
        println("Getting value for \${property.name}")  
        return value  
    }  

    operator fun setValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>, newValue: String) {  
        println("Setting value for \${property.name} to \$newValue")  
        value = newValue  
    }  
}  

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

fun main() {  
    val example = Example()  
    example.text = "Hello, Kotlin!"  
    println(example.text)  
}  

Output

Setting value for text to Hello, Kotlin!  
Getting value for text  
Hello, Kotlin!  

In this example:

  • The StringDelegate class defines custom behavior for property access using the getValue and setValue operators.
  • The text property in the Example class delegates its behavior to an instance of StringDelegate.

Real-World Applications of Delegates

1️⃣ Dependency Injection with Koin

In #Koin, you can use the by inject() delegate to inject dependencies directly into your classes. This eliminates the need for manual instantiation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class DelegatesFragment : Fragment() {  
    private val tracker: AnalyticsTracker by inject()  
}

inline fun <reified T : Any> KoinComponent.inject(
    qualifier: Qualifier? = null,
    mode: LazyThreadSafetyMode = KoinPlatformTools.defaultLazyMode(),
    noinline parameters: ParametersDefinition? = null,
): Lazy<T> =
    lazy(mode) { get<T>(qualifier, parameters) }

The by inject() delegate automatically resolves the dependency using Koin’s container. It abstracts the boilerplate, resulting in cleaner, testable code.


2️⃣ State Management in Jetpack Compose

In Jetpack Compose, the remember function with mutableStateOf is a great example of delegation. It helps you manage state efficiently within your composables:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Composable  
fun Counter() {  
    var count by remember { mutableStateOf(0) }  

    Column {  
        Text("Count: $count")  
        Button(onClick = { count++ }) {  
            Text("Increment")  
        }  
    }  
}  

3️⃣ Lazy Initialization

The lazy delegate is perfect for properties that need to be initialized only when accessed for the first time:

1
2
3
4
5
6
7
8
9
val greeting: String by lazy {  
    println("Initializing...")  
    "Hello, Kotlin!"  
}  

fun main() {  
    println(greeting) // Initializes here  
    println(greeting) // Uses cached value  
}  

Output

Initializing...  
Hello, Kotlin!  
Hello, Kotlin!  

4️⃣ Interface Delegation in Constructor

Kotlin allows you to delegate the implementation of an interface to another object.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface Logger {  
    fun log(message: String)  
}  

class ConsoleLogger : Logger {  
    override fun log(message: String) {  
        println("Log: $message")  
    }  
}  

class FileLogger : Logger {  
    override fun log(message: String) {  
        println("Writing log to file: $message")  
    }  
}  

class Application(logger: Logger) : Logger by logger  

fun main() {  
    val consoleApp = Application(ConsoleLogger())  
    consoleApp.log("Starting console application")  

    val fileApp = Application(FileLogger())  
    fileApp.log("Starting file application")  
}  

Output

Log: Starting console application  
Writing log to file: Starting file application  

Here’s what’s happening:

  • The Application class doesn’t implement the Logger methods directly.
  • Instead, it delegates the Logger implementation to the object passed to its constructor using by.
  • This makes it easy to swap implementations without changing the Application class.

Why Use Kotlin Delegates?

Delegates encapsulate logic that would otherwise clutter your classes. They help:

  • Simplify code by reusing logic (e.g., lazy initialization).
  • Abstract repetitive patterns (e.g., dependency injection).
  • Enhance state management (e.g., Jetpack Compose’s remember).
  • Provide modular and reusable interface implementations (e.g., constructor delegation).

Conclusion

Kotlin’s delegate mechanism is a prime example of how the language combines simplicity and power. Delegates are everywhere in Kotlin development. Are you using them in other cases in your projects?

Share on

Ignacio Carrión
WRITTEN BY
Ignacio Carrión
Android Developer