Observer pattern in Kotlin

Also known as Publish / Subscribe Pattern, Signal-Slot Pattern. Part of Event-Driven Architecture. In this tutorial, we will show you how to implement the observer pattern in Kotlin. Also, we will see which functionality Kotlin natively provides and how it is used in the Android app development ecosystem.

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

GoF – Design Patterns

Observer Pattern Example in Kotlin

To demonstrate the usage of this design pattern we will describe a simple use case from the real world. Imagine a temperature sensor which is measuring the temperature every x – seconds. The number is arbitrary, but we are not really in control of it. We have a small screen that displays the temperature input from the sensor. To do so, it must ask the sensor about the temperature.

The monitor must call the sensor repeatedly to show the correct temperature. The problem is obvious: the monitor needs to time The monitor must call the sensor repeatedly to show the correct temperature. The problem is obvious: the monitor needs to time perfectly with the sensor’s reading rate. If it is faster (the monitor asks more often than the temperature changes) it uses too many resources. If it is slower, the temperature might be not updated fast enough. The currently described system is a so-called “Pull – Mechanism”.

Standard Implementation

With the Observer pattern, it is possible to revert the Pull System to a Push – Mechanism. In this case, the sensor will inherit from a base class calledSubject. Observers can be attached/detached to a subject. The subject can notify all attached observers, that its internal state has changed. It will call the abstract methodupdate(), which must be implemented by the Monitor. The relationship is shown in the following UML diagram.

Observer pattern in Kotlin

A potential implementation of the given example could be like the following code.

// Standard Observer Pattern
open class Subject {
    private var observers = mutableListOf<Observer>()

    fun callObservers() {
        for(obs in observers) obs.update()
    }

    fun attach(obs : Observer) {
        observers.add(obs)
    }

    fun detach(obs : Observer) {
        observers.remove(obs)
    }
}

interface Observer {
    fun update()
}

class Sensor : Subject() {
    var temperature: Int = 0
        set(value) {
            field = value
            callObservers()
        }
}

class Monitor(val sensor: Sensor) : Observer {
    init {
        sensor.attach(this)
    }
    
    override fun update() {
        val newTemperature = sensor.temperature
        println("update Monitor")
    }

}

fun main() {

    val sensor = Sensor()
    val monitor = Monitor(sensor)

    sensor.temperature = 5
}

This is the most simple form of the Publish / Subscribe pattern. It already serves the purpose to implement a push-mechanism. However, the subscribed objects are not aware of which part of the subject has changed. If you need more control you should consider the signal slot implementation.

Build-In observable delegate in Kotlin

Kotlin provides some build-in functionality. Details can be found inLINK. The idea is to wrap a property by a delegate. The delegates give a callback function each time the observed value changes. A simple example could look like that.

// Kotlin observable
class Sensor {
var temperature: Int by Delegates.observable(0) { property, oldValue, newValue -> onChange()}

private fun onChange() {

}
}

This is quite useful if you need to trigger actions when the value changes. Pus for this approach is that both values (old and new) are available. This gives an add-on in flexibility. In most times however you will find that you can call internal members and properties easy but external observers are difficult to call. In our example, we have 1 to many relationships. The sensor needs to have a list of objects which are listening to the state change. So in the end you will need again the same interfaces and base classes as in the standard implementation. You can read more about delegators in ourdelegates tutorial.

Signal – Slot Mechanism

A common implementation of the observer pattern is the signal slot mechanism. In this way, theobservableis a templated class, which is called a signal. Every other class can havesignalobjects (composition). Other clients (e.g. classes) can connect to the signal by providing a slot that fits the required interface. A slot is general a function that has the same interface as the signal. The above example could be implemented with signal-slots in the following way.

// Signal - Slot
class Signal<TType> {

    class Connection

    val callbacks = mutableMapOf<Connection, (TType) -> Unit>()

    fun emit(newValue: TType) {
        for(cb in callbacks) cb.value(newValue)
    }

    fun connect(callback: (newValue: TType) -> Unit) : Connection {
        val connection = Connection()
        callbacks[connection] = callback
        return connection
    }

    fun disconnect(connection : Connection) {
        callbacks.remove(connection)
    }
}

class Sensor {
    val temperatureChanged = Signal<Int>()
}

class Monitor {

    fun onTemperatureChanged(newTemperature : Int) {
        // doSomething
    }
}

fun main() {

    val sensor = Sensor()
    val monitor = Monitor()
    sensor.tempChanged.connect(monitor::onTemperatureChanged)

    sensor.tempChanged.emit(5)
}

This signal-slot example is very simplistic. Thesignalis a template class, which has 3 methods. Theconnectmethod registers a callback with the desired interface. It returns aconnectionobject (here an empty class). Thisconnectionobject is necessary to avoid potential memory leaks, as the signal effectively increases the reference count of connected observers. To release a callback, thedisconnectfunction can be called. Theemitfunction is calling all callbacks with the new value. This implementation is not improved for performance, but it shows the main points of a signal-slot implementation.

The advantage of such an implementation is that every object can become observable and every object can be observed, without inheriting a specific interface. It favors composition over inheritance. Secondly, a signal can be called not only when a value changes, but also in different circumstances. Third, it provides flexibility to connect a slot. The above example could be connected with:

    // Slot variants
    val sensor = Sensor()

    // Connect member function
    val monitor = Monitor()
    sensor.temperatureChanged.connect(monitor::onTemperatureChanged)

    // Connect anonymous function
    sensor.temperatureChanged.connect(fun (newValue : Int) { println("anonymous function")})

    // Connect lambda
    sensor.temperatureChanged.connect{newValue: Int ->  println("lambda function")}

Android App Development

In the section above we have covered the general use cases of the Subscribe / Publish pattern in Kotlin. In the current section, we will focus on the Android application ecosystem.

So what is an observer in Android? And what are the typical use cases?

The observer pattern is commonly used in combination with an MVC (Model-View-Controller) or MVVM (Model-View-ViewModel) architecture. It works well because the UI is automatically updated, once Domain objects are changed. Often this is applied with Property binding.

OnClickListener / handler

A common use case isOnClickListeners, which often also are called handlers. They appear in UI components that can be clicked by the user. You can define the action which needs to be performed by setting the listener (observer) to the button (subject). The following code illustrates that. Kotlin provides several ways to attach listeners. The current example uses a lambda function.

// Android Button OnClickListener
class MainActivity : AppCompatActivity() {

    private lateinit var button: Button
    private lateinit var text: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button = findViewById(R.id.myBtton)
        text = findViewById(R.id.myTextView)

        // By lambda
        button.setOnClickListener {
            text.text = "Clicked"
        }
    }
}
Android Button OnClickListener in Kotlin

JetPack – LiveData

Androids JetPack library provides as well an implementation for the Observer pattern. In this post, we show a simplified use case how to use it. To read more in detail please check the official documentation ofJetPack – LiveData.

In the past LiveData was often used in function with CoRoutines. Now with the new Kotlin flow the question is:

Is LiveData deprecated?

We can say that for Kotlin-only apps, this is certainly the case when it is used to propagate changes coroutines. However, for common MVVM or MVC applications it is still used frequently. Therefore we will show the above example with LiveData.

So, how do you observe live data in Kotlin? First, you need to adapt your Gradle script to have access to the LiveData implementation. The actual version depends on your setup and when you’re reading this post.

implementation 'androidx.lifecycle:lifecycle-livedata:2.3.0'

With that, you should be able to include the templated classes. For our “Button clicked” example we will add a new text field. Once the button is clicked it will change the value of the LiveData object. There are two registered listeners to this object. Both will change the text of one of the text fields. This is just to show that with this approach we can register several objects to one subject.

// Android LiveData
class MainActivity : AppCompatActivity() {

    private lateinit var button: Button
    private lateinit var text: TextView
    private lateinit var otherText: TextView

    private var wasClicked = MutableLiveData<Boolean>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button = findViewById(R.id.myButton)
        text = findViewById(R.id.myTextView)
        otherText = findViewById(R.id.otherTextView)

        button.setOnClickListener {
            wasClicked.value = true
        }

        wasClicked.observe(this, {
            text.text = "Clicked"
        })

        wasClicked.observe(this, {
            otherText.text = "Also Clicked"
        })

    }
}
Android LiveData in Kotlin