JohnB
JohnB

Reputation: 13713

Observable property allowing to add observers at runtime

Via Delegates.observable, Kotlin permits observable properties. I need, however, the ability of adding observers at runtime, as Java's Observable class does.

What I have now, is the following:

import java.util.*
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
import kotlin.reflect.jvm.isAccessible


class MyObservable<T> (var v: T): java.util.Observable() {

    operator fun getValue(thisRef: Any, prop: KProperty<*>) = v
    operator fun setValue(thisRef: Any, prop: KProperty<*>, newValue: T) {
        v = newValue
        setChanged()
        notifyObservers()
    }
}

fun <T> addObserver(prop: KProperty0<T>, observerFn: (T) -> Unit) =
        (prop.apply{ isAccessible = true }.getDelegate() as MyObservable<T>)
                .addObserver(Observer({ o, _ -> observerFn((o as MyObservable<T>).v) }))


class ObservableExample {
    var i: Int by MyObservable(3)
}

fun main(args: Array<String>) {
    val ex: ObservableExample = ObservableExample();

    addObserver(ex::i, { println(it) })

    ex.i = 7
    ex.i = 9

    // prints:
    // 7
    // 9
}

It works, but it feels like reinventing the wheel.

Isn't there a standard solution for this?

If not, is what I've done correct?

Upvotes: 3

Views: 3859

Answers (2)

holi-java
holi-java

Reputation: 30686

This is because you starts with a simple example, and can't find the benefits of Kotlin delegated properties.

Kotlin doesn't forcing you to implements any interface to supports delegated properties, yon can using delegated properties in Kotlin just provide getValue & setValue(?) operators. and their visibility even can be private.

Kotlin provided a provideDelegate operator function since 1.1, that let you manage/control how to create a delegate.

The delegate in Kotlin is working in the background, which means it is invisible from the source code point of view, and let the code source treat a delegated properties as a regular properties.

Kotlin delegated properties can easily let you manage java beans without using PropertyEditorSupport in Java, and you don't need to manage the delegate at all in Kotlin, just to notify the changed property only. for example:

val history = mutableMapOf<String, MutableList<Pair<Any?, Any?>>>()
val subject = Subject()

subject.subscribe { event ->
    val each = history.getOrPut(event.propertyName) { mutableListOf() }
    each.add(event.oldValue to event.newValue)
}

//      v--- treat a delegated property as regular property
subject.number = 1
subject.string = "bar"
subject.number = 2

println(history);
//      ^--- {"number":[<null,1>,<1,2>], "string": [<null,"bar">]}

Note: the getValue & setValue operator functions private below.

class Subject {
    //                     v--- manage the delegated property internally
    var string: String? by this
    var number: Int? by this

    private val properties by lazy {
        mutableMapOf<Any?, Any?>()
    }
    private val listeners by lazy {
        mutableListOf<PropertyChangeListener>()
    }

    private operator @Suppress("UNCHECKED_CAST")
    fun <T : Any?> getValue(self: Any, prop: KProperty<*>): T {
        return properties[prop.name] as T
    }

    private operator 
    fun <T : Any?> setValue(self: Any,prop: KProperty<*>, newValue: T) {
        val event = PropertyChangeEvent(
                self,
                prop.name,
                properties[prop.name],
                newValue
        )
        properties[prop.name] = newValue
        listeners.forEach { it.propertyChange(event) }
    }

    fun subscribe(listener: (event: PropertyChangeEvent) -> Unit) {
        subscribe(PropertyChangeListener { listener(it) })
    }

    fun subscribe(subscriber: PropertyChangeListener) {
        listeners.add(subscriber)
    }
}

Upvotes: 0

sedovav
sedovav

Reputation: 2046

A slightly shorter variant of the same idea:

import kotlin.properties.Delegates

typealias IntObserver = (Int) -> Unit

class ObservableExample {
    val prop1Observers = mutableListOf<IntObserver>()

    var prop1: Int by Delegates.observable(0) { prop, old, new ->
        prop1Observers.forEach { it(new) }
    }
}

fun main(args: Array<String>) {
    val example = ObservableExample()
    example.prop1Observers.add({ println(it) })
    example.prop1 = 1
    example.prop1 = 2
}

The output is as expected. Probably, it is better to make observers property private and add a method to add subscribers but I omitted it for the simplicity.

Upvotes: 2

Related Questions