Skip to Content
Course content

53: Property Delegation with by lazy and Delegates.observable

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 hardwareInterface that uses by 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) using Delegates.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.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.