What does Delegates.observable do, and how do you use it to run code every time a property changes?
answer
- observable(initial) { prop, old, new -> }
- Callback runs AFTER the write
- Cannot reject — only reacts
- Lives in kotlin.properties.Delegates
- Fires even if new == old
basics
~20 sDelegates.observable lets a property call a small function every time you assign it a new value. You give it a starting value and a callback that runs after each change, so you can react to updates.
solid answer
~30 sDelegates.observable(initialValue) { property, oldValue, newValue -> ... } creates a delegate for a `var` using the `by` keyword. You pass an initial value and a lambda. After every assignment the lambda runs, receiving the KProperty metadata, the previous value, and the new value. The callback fires *after* the field is already updated, so it cannot block the change (unlike vetoable). Typical uses: logging, triggering UI refresh, or invalidating a cache when state changes. It lives in `kotlin.properties.Delegates`. Because it only reacts, it never affects whether the assignment succeeds.
code
kotlin · 10 linesimport kotlin.properties.Delegates
var status: String by Delegates.observable("idle") { _, old, new ->
println("status: $old -> $new")
}
fun main() {
status = "running" // status: idle -> running
status = "done" // status: running -> done
}go deeper
Knows it runs a callback on each assignment and uses by + initial value correctly.
Explains the after-the-write timing and the three lambda parameters, distinguishes it from vetoable.
Notes it fires even on equal values, that it returns a ReadWriteProperty, and points to invalidation/caching use cases.
Discusses thread-safety expectations, reentrancy if onChange reassigns the property, and when an explicit setter or Flow is a better fit than a delegate.
## What it is `Delegates.observable` is a factory in the `kotlin.properties.Delegates` object that returns a *property delegate*. A delegate is an object that the `by` keyword hands a property's get/set logic to. You attach it to a `var`, and Kotlin routes every read/write through the delegate. ## Signature and parameters ```kotlin fun <T> observable( initialValue: T, onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit ): ReadWriteProperty<Any?, T> ``` - `initialValue` — the value the property starts with. - `onChange` — a lambda invoked **after** each assignment completes. Its parameters are: - `property: KProperty<*>` — reflection metadata; `property.name` gives the property name. - `oldValue` — the value before this assignment. - `newValue` — the value just stored. ## Key timing rule The callback runs **after** the new value is already written to the backing field. So `observable` can only *react*; it cannot reject or alter the change. (To reject, use `vetoable`.) ## Example ```kotlin import kotlin.properties.Delegates class User { var name: String by Delegates.observable("<no name>") { prop, old, new -> println("${prop.name} changed: '$old' -> '$new'") } } fun main() { val u = User() u.name = "Alice" // prints: name changed: '<no name>' -> 'Alice' u.name = "Bob" // prints: name changed: 'Alice' -> 'Bob' } ``` ## When it does NOT fire The callback only fires on **assignment** (`=`). Reading the property does not trigger it. Assigning the *same* value still fires the callback (observable does not compare for equality before calling). ## Common uses - Logging/auditing state changes. - Marking a view 'dirty' so it re-renders. - Keeping a derived field or cache in sync.
- Does the callback fire before or after the field is updated?After. The new value is already stored when onChange runs, which is why observable cannot reject a change.
- What are the three parameters of the onChange lambda?The KProperty metadata, the old value, and the new value.
Like a doorbell that rings after someone has already walked in — you find out about the change, but you can't stop them entering.
saying these in an interview costs you the question
- Saying observable can prevent/cancel an assignment (that's vetoable)
- Claiming the callback runs on property reads
- Thinking it only fires when the value actually differs
- Confusing it with by lazy
- Not knowing it requires the by keyword