article banner (priority)

Observable and Vetoable delegates

This is a chapter from the book Advanced Kotlin. You can find it on LeanPub.

The next important delegate from Kotlin stdlib is observable from the Delegates object, which makes the property behave like a regular property, but also specifies a function that will be executed whenever the property 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 includes three parameters: reference to the property, value before the change, and new value. An observable delegate can be replaced with the following setter1:

var prop: SomeType by Delegates.observable(initial,operation) // Alternative to var prop: SomeType = initial set(newValue) { field = newValue operation(::prop, field, newValue) }

Notice that elements from object declarations can be imported directly (it is known as a static import) and then can be used directly.

import kotlin.properties.Delegates.observable val prop: SomeType by observable(initial, operation)

We use the observable delegate when we want to take some action whenever the property value changes. For instance, 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 on a list, whenever the list of elements changes, we should redraw the view. I often used observable delegate to do it automatically.

var elements by observable(elements) { _, old, new -> if (new != old) notifyDataSetChanged() }

We use observable delegate to invoke some action on property change. We might use it to implement a class that invokes observers whenever 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 observable delegate, one property change can influence other properties or our application state. I used that on some applications to implement unidirectional data binding. For instance, when I wanted to define a property whose state change influences changes in view. Like the drawerOpen property from the example below, which opens the drawer when set to true, and closes the drawer when set to false2.

var drawerOpen by observable(false) { _, _, open -> if (open) drawerLayout.openDrawer(GravityCompat.START) else drawerLayout.closeDrawer(GravityCompat.START) }

Notice, that property delegate can be extracted into a separate function, 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 book id and the page number. Let’s assume that changing the book the user is reading means resetting the page number. We can ensure that 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 observable delegate to interact with the property value itself. For instance, on one project, we decided for a presenter to have sub-presenters. Each of them should have its own lifecycle, so the onCreate and onDestroy methods should be called when the presenter is added or removed. To never forget about those function calls, we can invoke them whenever the presenter's list is changed. So after each change, we take presenters that are now but were not before, so are new, and we call onCreate on them, and we take presenters that were before and are not now, so were removed, and we call onDestroy on them.

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 how observable delegate can be used. Now let's talk about a brother of this delegate, which also seems useful, but in practice, is used much less often.

vetoable delegate

"Veto" is a Latin word meaning "I forbid"3. The vetoable delegate is very similar to observable, but it can forbid property value change. That is why the vetoable lambda expression is executed before the property value changes, and it returns the Boolean type that decides if the property value should change or not. If this function returns true, the property value will change; if it returns false, the property 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 } }

Here is a complete usage example:

import kotlin.properties.Delegates.vetoable var smallList: List<String> by vetoable(emptyList()) { _, _, new -> println(new) new.size <= 3 } fun main() { smallList = listOf("A", "B", "C") // [A, B, C] println(smallList) // [A, B, C] smallList = listOf("D", "E", "F", "G") // [D, E, F, G] println(smallList) // [A, B, C] smallList = listOf("H") // [H] println(smallList) // [H] }

The vetoable delegate can be used when we have a property with some requirements on its value, and 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, vetoable delegate is not used very often, but some practical examples might include allowing only specific state changes in an application, or requiring 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 observable and vetoable delegates. In the next part, you will learn how Map can be used as a delegate.

1:

Not in all the cases, because the observable delegate has better synchronization, but it can help us understand how observable works in simplification.

2:

I pushed this pattern further in the KotlinAndroidViewBindings library, which makes little sense in modern android development thanks to good Android support for LiveData or StateFlow.

3:

This word is well-known in Poland due to historical reasons. You can read about Liberum Veto.