Skip to Content
Course content

213: vetoable and observable Delegates In Depth

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

By now, you're comfortable with the basic idea of property delegation. But in real-world production code, we rarely just delegate to a lazy value or a map. We usually need to hook into the lifecycle of a property change. That's where observable and vetoable come in. They allow us to treat a property assignment like an event.

Let's build a Thermostat controller. It's a simple example, but it perfectly illustrates why you'd pick one delegate over the other. We need to do two things: we need to log every time the temperature changes (for audit trails), and we need to make sure the temperature stays within a safe range so we don't accidentally blow a fuse or freeze the pipes.

Tracking temperature shifts with observable

First, I want a way to trigger a system update whenever the targetTemperature changes. I don't want to write a custom setter for every single property in my class—that's a maintenance nightmare. Instead, I'll use Delegates.observable.

import kotlin.properties.Delegates

class Thermostat {
    var targetTemperature: Int by Delegates.observable(20) { prop, old, new ->
        println("Temperature changed from $old to $new. Updating HVAC system...")
    }
}

fun main() {
    val homeHeat = Thermostat()
    homeHeat.targetTemperature = 22 // Prints: Temperature changed from 20 to 22...
}


Notice how the lambda gives us the property object, the old value, and the new value. It's clean, and it happens after the value has already been updated. This is great for side effects, like updating a UI or sending a network packet.

Preventing dangerous settings with vetoable

Now, here is where things get risky. A thermostat shouldn't allow a temperature of 100°C or -50°C. If I use observable, I can see the value changed, but I can't stop it. The damage is already done.

This is where Delegates.vetoable shines. It intercepts the assignment before it happens. If the lambda returns false, the assignment is rejected, and the property keeps its old value.

class Thermostat {
    var targetTemperature: Int by Delegates.vetoable(20) { prop, old, new ->
        if (new in 5..30) {
            println("Setting temperature to $new")
            true // Accept the change
        } else {
            println("Rejected $new: Temperature out of safe range!")
            false // Veto the change
        }
    }
}


The "Too Late" mistake I made

I'll be honest: when I first started using these, I tried to combine them by putting validation logic inside an observable delegate. I thought I could just set the value back to the old one if the new one was invalid. It looked like this:

// DON'T DO THIS
var targetTemperature: Int by Delegates.observable(20) { prop, old, new ->
    if (new > 30) {
        targetTemperature = old // Trying to "undo" the change
    }
}


I quickly realized this creates a recursive loop. Setting targetTemperature = old inside the observable trigger fires the observable again, which checks the value, which might set it again... and boom, you've got a StackOverflowError. If you need to stop a value from being set, vetoable is the only way to go. It's a guard rail, not a cleanup crew.

Combining both for a robust system

In a real project, you might have multiple properties with different needs. You might have a targetTemperature that is vetoable (to prevent danger) and a systemStatus string that is observable (to log status changes).

class Thermostat {
    var targetTemperature: Int by Delegates.vetoable(20) { _, _, new ->
        new in 5..30
    }

    var systemStatus: String by Delegates.observable("Idle") { _, old, new ->
        println("System status shifted from $old to $new")
    }
}


Keep in mind that vetoable is essentially a filter. If it returns false, the setter is effectively ignored. When you're designing your classes, ask yourself: "Do I need to react to a change (Observable), or do I need to permit a change (Vetoable)?"




📋 Practical Task

Build a Vetoable Budget Guard

You are building a simple financial tracker. Create a class called BudgetTracker with the following requirements:

  • A property balance (Double) that uses Delegates.observable to print "Balance updated to [new value]" every time it changes.
  • A property spendingLimit (Double) that uses Delegates.vetoable. It should only allow the limit to be updated if the new value is positive (greater than 0). If the value is 0 or negative, it should veto the change and print "Invalid limit rejected".

Write a main function to test both properties: try to set a valid limit, an invalid limit, and change the balance.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.