The next important delegate from Kotlin stdlib is observable from the Delegates object, which makes a property behave like a regular property but also specifies a function that will be executed whenever the property’s setter is called.
var name: String by
Delegates.observable("Empty") { prop, old, new ->
println("$old -> $new")
}
fun main() {
name = "Martin" // Empty -> Martin
name = "Igor" // Martin -> Igor
name = "Igor" // Igor -> Igor
}
This lambda expression has three parameters: a reference to the property, the value before the change, and the new value. An observable delegate can be replaced with the following setter[^33_1]:
var prop: SomeType by Delegates.observable(initial, operation)
// Alternative to
var prop: SomeType = initial
set(newValue) {
field = newValue
operation(::prop, field, newValue)
}
Note that elements from object declarations can be imported directly (known as a static import) and then used directly.
import kotlin.properties.Delegates.observable
val prop: SomeType by observable(initial, operation)
We use the observable delegate if we want to take some action whenever a property value changes, e.g., when we want to log each property change:
val status by observable(INITIAL) { _, old, new ->
log.info("Status changed from $old to $new")
}
This way, all property changes will be displayed in logs, and we can easily track them. On Android, the observable delegate is often used when a property change should lead to a view update. For instance, when we implement a list adapter (a class that decides how and what elements should be displayed in a list), we should redraw the view whenever the list of elements changes. I often use an observable delegate to do this automatically.
var elements by observable(elements) { _, old, new ->
if (new != old) notifyDataSetChanged()
}
We use an observable delegate to invoke some action when a property changes. We might use it to implement a class that invokes observers whenever an observable property changes.
import kotlin.properties.Delegates.observable
class ObservableProperty<T>(initial: T) {
private var observers: List<(T) -> Unit> = listOf()
var value: T by observable(initial) { _, _, new ->
observers.forEach { it(new) }
}
fun addObserver(observer: (T) -> Unit) {
observers += observer
}
}
fun main() {
val name = ObservableProperty("")
name.addObserver { println("Changed to $it") }
name.value = "A"
// Changed to A
name.addObserver { println("Now it is $it") }
name.value = "B"
// Changed to B
// Now it is B
}
Using an observable delegate, one property change can influence other properties or our application state. I’ve used this to implement unidirectional data binding when, for instance, I wanted to define a property whose state change influenced changes in a view. This is like the drawerOpen property in the example below, which opens and closes the drawer when set to true or false[^33_2].
var drawerOpen by observable(false) { _, _, open ->
if (open) drawerLayout.openDrawer(GravityCompat.START)
else drawerLayout.closeDrawer(GravityCompat.START)
}
Note that a property delegate can be extracted into a separate function that is reusable between components.
var drawerOpen by bindToDrawerOpen(false) { drawerLayout }
fun bindToDrawerOpen(
initial: Boolean,
lazyView: () -> DrawerLayout
) = observable(initial) { _, _, open ->
if (open) drawerLayout.openDrawer(GravityCompat.START)
else drawerLayout.closeDrawer(GravityCompat.START)
}
Another example might be when we write an application for reading books, and we have properties to represent the book id and page number. Let’s assume that changing the book the user is reading means resetting the page number, which we can do using an observable delegate.
import kotlin.properties.Delegates.observable
var book: String by observable("") { _, _, _ ->
page = 0
}
var page = 0
fun main() {
book = "TheWitcher"
repeat(69) { page++ }
println(book) // TheWitcher
println(page) // 69
book = "Ice"
println(book) // Ice
println(page) // 0
}
We can also use an observable delegate to interact with the property value itself. For instance, for one project we decided that a presenter should have sub-presenters, each of which should have its own lifecycle, therefore the onCreate and onDestroy methods should be called when a presenter is added or removed. In order to never forget about these function calls, we can invoke them whenever the list of presenters is changed. After each change, we call onCreate on the new presenters (those that are now but were not before), and we call onDestroy on the removed presenters (those that were before and are not now).
var presenters: List<Presenter> by
observable(emptyList()) { _, old, new ->
(new - old).forEach { it.onCreate() }
(old - new).forEach { it.onDestroy() }
}
As you can see, there are many practical ways in which an observable delegate can be used. Now, let's talk about this delegate’s brother, which also seems useful but is used much less often in practice.
The vetoable delegate
"Veto" is a Latin word meaning "I forbid"[^33_3]. The vetoable delegate is very similar to observable, but it can forbid property value changes. That is why the vetoable lambda expression is executed before the property value changes and returns the Boolean type, which determines if the property value should change or not. If this function returns true, the property value will change; if it returns false, the value will not change.
var prop: SomeType by Delegates.vetoable(initial, operation)
// Alternative to
var prop: SomeType = initial
set(newValue) {
if (operation(::prop, field, newValue)) {
field = newValue
}
}
The vetoable delegate can be used when we have a property with some requirements on its value; whenever someone tries to modify this value, we first need to validate the new value. We might also invoke some actions when a new value is valid (like displaying it) or when it is not (like logging an error). So, this is a conceptual presentation of how vetoable could be used:
var name: String by vetoable("") { _, _, new ->
if (isValid(new)) {
showNewName(new)
true
} else {
showNameError()
false
}
}
In practice, the vetoable delegate is not used very often, but some practical examples might include allowing only specific state changes in an application, or requiring only valid values.
import kotlin.properties.Delegates.vetoable
val state by vetoable(Initial) { _, _, newState ->
if (newState is Initial) {
log.e("Cannot set initial state")
return@vetoable false
}
// ...
return@vetoable true
}
val email by vetoable(email) { _, _, newEmail ->
emailRegex.matches(newEmail)
}
I hope you enjoyed the observable and vetoable delegates. In the next part, you will learn how Map can be used as a delegate.
[^33_1]: Not in all cases because the observable delegate has better synchronization, but this simplified code can help us understand how observable works in general.
[^33_2]: I pushed this pattern further in the KotlinAndroidViewBindings library, which makes little sense in modern Android development because it now has good support for LiveData and StateFlow.
[^33_3]: This word is well-known in Poland for historical reasons. You can read about Liberum Veto.
Marcin Moskala is a highly experienced developer and Kotlin instructor as the founder of Kt. Academy, an official JetBrains partner specializing in Kotlin training, Google Developers Expert, known for his significant contributions to the Kotlin community. Moskala is the author of several widely recognized books, including "Effective Kotlin," "Kotlin Coroutines," "Functional Kotlin," "Advanced Kotlin," "Kotlin Essentials," and "Android Development with Kotlin."
Beyond his literary achievements, Moskala is the author of the largest Medium publication dedicated to Kotlin. As a respected speaker, he has been invited to share his insights at numerous programming conferences, including events such as Droidcon and the prestigious Kotlin Conf, the premier conference dedicated to the Kotlin programming language.
Software architect with 15 years of experience, currently working on building infrastructure for AI. I think Kotlin is one of the best programming languages ever created.
Owen has been developing software since the mid 1990s and remembers the productivity of languages such as Clipper and Borland Delphi.
Since 2001, He moved to Web, Server based Java and the Open Source revolution.
With many years of commercial Java experience, He picked up on Kotlin in early 2015.
After taking detours into Clojure and Scala, like Goldilocks, He thinks Kotlin is just right and tastes the best.
Owen enthusiastically helps Kotlin developers continue to succeed.