-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
53: Property Delegation with by lazy and Delegates.observable
I've seen this pattern countless times in production code: a class that takes forever to instantiate because it's eagerly loading every single dependency it might possibly need, even if those dependencies aren't used until much later in the application lifecycle. Then, to make matters worse, the developer tries to track state changes by manually calling "update" functions every time a variable is assigned, eventually forgetting one of those calls and spending three hours debugging a UI that won't refresh.
Take a look at this UserSessionManager. It's trying to handle a heavy database connection and track a user's status, but it's doing it the hard way:
class UserSessionManager {
// This happens the moment the class is created.
// If DatabaseConnection is slow, your app freezes here.
val database = DatabaseConnection()
var userStatus: String = "Offline"
fun updateStatus(newStatus: String) {
userStatus = newStatus
logStatusChange() // Manually calling the observer logic
}
fun quickChangeStatus(newStatus: String) {
// Oops! I forgot to call logStatusChange() here.
// Now the status changed, but the logs are lying to us.
userStatus = newStatus
}
private fun logStatusChange() {
println("Status changed to: $userStatus")
}
}
Stopping the Startup Lag with by lazy
The first problem is the database property. We're paying the performance cost of DatabaseConnection() immediately. But does the manager need the database the millisecond it's created? Probably not. It only needs it when the first query is actually run.
In Kotlin, we use by lazy for this. It's a property delegate that tells the compiler: "Don't run this block of code until someone actually asks for the value of this property. Once you do run it, remember the result and return that same result every time after."
// Instead of: val database = DatabaseConnection()
val database: DatabaseConnection by lazy {
println("Connecting to DB... (this is slow)")
DatabaseConnection()
}
Now, the UserSessionManager instantiates instantly. The "slow" connection logic only triggers the very first time you call manager.database. I personally love by lazy for heavy resources like API clients, database handles, or complex regex patterns that are expensive to compile.
Automating State Tracking with Delegates.observable
Now let's fix that bug in userStatus. Relying on the developer to remember to call logStatusChange() every single time the variable is updated is a recipe for disaster. We want the tracking logic to be tied to the property itself, regardless of where the assignment happens.
Enter Delegates.observable. This delegate allows you to define a callback that triggers every time the property value changes. The cool part is that the callback gives you the oldValue and the newValue, which is incredibly useful for logging or triggering UI refreshes.
import kotlin.properties.Delegates
var userStatus: String by Delegates.observable("Offline") { property, oldValue, newValue ->
println("Status changed from $oldValue to $newValue")
// Any logic here runs automatically on every assignment
}
With this setup, quickChangeStatus no longer needs to worry about logging. The delegate handles it behind the scenes. You've effectively separated the action (changing the status) from the reaction (logging the change).
Putting it all together
Here is the refactored, professional version of that manager. It's leaner, faster to start, and logically bulletproof regarding its state changes.
import kotlin.properties.Delegates
class UserSessionManager {
// Initialized only when first accessed
val database: DatabaseConnection by lazy {
DatabaseConnection()
}
// Automatically logs every time it's changed
var userStatus: String by Delegates.observable("Offline") { _, old, new ->
println("Log: User transitioned from $old to $new")
}
fun updateStatus(newStatus: String) {
userStatus = newStatus // The observer triggers automatically!
}
fun quickChangeStatus(newStatus: String) {
userStatus = newStatus // Still triggers! No more bugs.
}
}📋 Practical Task
Build a Smart Thermostat Controller
You are building a controller for a smart home thermostat. You need to implement two specific behaviors using property delegation:
- Delayed Hardware Connection: Create a property called
hardwareInterfacethat usesby lazy. It should simulate a slow connection by printing "Initializing hardware sensors..." before returning a mock Hardware object. - Temperature Monitoring: Create a property called
currentTemperature(a Double) usingDelegates.observable. The observer should print a warning message ("ALERT: Temperature exceeded 30°C!") only if the new temperature is above 30.0.
Requirements:
1. Create a simple mock class Hardware() to be returned by the lazy delegate.
2. In your main function, instantiate your controller.
3. Verify that the "Initializing..." message does NOT appear until you actually access the hardwareInterface.
4. Change the currentTemperature multiple times to test that the observer triggers correctly and only alerts when the threshold is crossed.
There are no comments for now.