don’t think so.
You can create a delegate yourself.
by calling this::name1.getDelegate()
you get the delegated property.
So, if you create an extensionFunction like to which does something like (
fun KProperty<R>.registerListener(listener: (old: R, new: R) -> Unit) {
(KProperty<R>.getDelegate() as YourDelegationClass).registerListener(listener)
}
you can call it like:
this::name1.registerListener{old, new -> }
Note, the registerListener is added to all the methods.
Maybe another way to do it is creating a manager and adding the listeners to that manager…
challenge
I challenge you to come up with an ObservableManager, where you register the listeners using:
manager.registerListener(this::name1){old, new -> ...}
and you can register using a one-liner, so no additional setup:
val test by function(defaultValue)
where function can come from anywhere (but every type should be able to call the same function.
bonus-points if you can do it, without using the full reflection.
found it 
solution in steps
The steps:
-
We specify a typealias for the listeners
typealias BiConsumer<O> = (O, O) -> Unit
-
We need a class, as we need to save some values. But we also want to let the pojo extend another class, therefor we use the combination of an interface and a class, this is a delegated class (or a trait).
-
In order to make the class only manage the properties of the POJO, we pass the class as generic.
interface IObservable<CLASS> {
val manager: ObservableManager<CLASS>
}
open class Observable<CLASS> : IObservable<CLASS> {
override val manager = ObservableManager<CLASS>()
}
-
To add the functionality to register properties and listeners, we use extension-functions, such that we don’t have to repeat us twice (once in the interface and once in the IObservable).
-
To safely register listeners, we use two generics: the one of the classType an the one for the propertyType. with those two properties. This function needs to be inlined, as we are going to use PROP for casting.
@Suppress("UNCHECKED_CAST")
inline fun <CLASS, reified PROP> IObservable<CLASS>.registerListener(
property: KProperty1<CLASS, PROP>,
crossinline consumer: BiConsumer<PROP>
) = manager.registerListener(property, consumer)
-
Inside our ObservableManager, we add the functionality for the listeners:
-
We add an inline function to convert a listener witch accepts two params of the propertyType to a listener witch accepts two params of type Any? and cast those params to the propertyType.
inline fun <reified PROP> registerListener(
property: KProperty<PROP>,
crossinline consumer: BiConsumer<PROP>
) = registerListener(property.name) { old, new -> consumer(old as PROP, new as PROP) }
-
Then we add the list of the propertyName to the converted consumers (we can safely use the propName, because we checked at registring if the properties being added are valid).
-
We only can make those listeners private if we add another not-inlined function to register the converted listeners.
private val listeners = mutableMapOf<String, MutableList<BiConsumer<Any?>>>()
fun registerListener(name: String, biConsumer: BiConsumer<Any?>) {
listeners.getOrPut(name) { mutableListOf() }.add(biConsumer)
}
-
The registration of the properties is less straigtforward.
We have four steps we need to make:
a. We need to create the extension-function which creates an object we can delegate to. We do this inline, so that we can create a lambda that casts a value of type Any? to PROP. We need this later.
@Suppress("UNCHECKED_CAST")
inline fun <reified S, T> IObservable<S>.observable(value: T) =
DelegateProvider(manager, value) { a: Any? -> a as T }
b. We have the genericType of the value and the initialValue. In order to get the name of the value, we need to return a class with provideDelegate. This function accepts two functions, the class and the property, which we pass on to an innerclass WrappingRWProperty, which we create in the next step. (innerclasses are called upon their receiver, so that we know of which type the innerclass is.
class ObservableValue<CLASS, PROP>(
private val manager: ObservableManager<S>,
val value: Any?,
private val converter: (Any?) -> PROP
) : DelegateProvider<CLASS, PROP> {
override operator fun provideDelegate(
thisRef: CLASS,
prop: KProperty<*>
): ReadWriteProperty<CLASS, PROP> = manager.WrappingRWProperty(prop, value, converter)
}
c. Now that we have everything we need, we provide the class with the real delegate. This is the class that has the getValue and setValue functions. These functions are the one being called by the field. This means that the class needs the PROP-generic for getting and setting the value.
We make this class an innerclass of ObservableManager, such that it can acces private methods/fields in ObservableManager. therefor we can store the properties inside the ObservableManager and noone can touch it.
inner class WrappingRWProperty<R>(
prop: KProperty<*>,
value: Any?,
private val converter: (Any?) -> R
) : ReadWriteProperty<T, R> {
//We can finally register the initial value ;-)
init { properties[property.name] = value }
override operator fun getValue(thisRef: T, property: KProperty<*>) =
properties[property.name].let(converter)
override operator fun setValue(thisRef: T, property: KProperty<*>, value: R) =
updateValue(property.name, value)
}
and of course, the ObservableManager also has the propertiesfielf and the updateValue, where it notifies the listener for updating.
class ObservableManager<T> {
private val properties = mutableMapOf<String, Any?>()
private fun updateValue(name: String, value: Any?) {
val old = properties.put(name, value)
listeners[name]?.forEach { it(old, value) }
}
...
}
note, doesn’t work well with inheritance, that is you shoudn’t delegate fields from a parent and his child to ObservableManager, as the values will be overwritten
code
typealias BiConsumer<O> = (O, O) -> Unit
class ObservableManager<T> {
private val properties = mutableMapOf<String, Any?>()
private val listeners = mutableMapOf<String, MutableList<BiConsumer<Any?>>>()
private fun updateValue(name: String, value: Any?) {
val old = properties.put(name, value)
listeners[name]?.forEach { it(old, value) }
}
fun registerListener(name: String, biConsumer: BiConsumer<Any?>) {
listeners.getOrPut(name) { mutableListOf() }.add(biConsumer)
}
inline fun <reified PROP> registerListener(property: KProperty1<T, PROP>, crossinline consumer: BiConsumer<PROP>) =
registerListener(property.name) { old, new -> consumer(old as PROP, new as PROP) }
inner class WrappingRWProperty<PROP>(
prop: KProperty<*>,
value: Any?,
private val converter: (Any?) -> PROP
) : ReadWriteProperty<T, PROP> {
//We can register the initial value ;-)
init {
properties[prop.name] = value
}
override operator fun getValue(thisRef: T, property: KProperty<*>) = properties[property.name].let(converter)
override operator fun setValue(thisRef: T, property: KProperty<*>, value: PROP) = updateValue(property.name, value)
}
}
class ObservableValue<CLASS, PROP>(
private val delegater: ObservableManager<CLASS>,
val value: Any?,
private val converter: (Any?) -> PROP
) : DelegateProvider<CLASS, PROP> {
override operator fun provideDelegate(
thisRef: CLASS,
prop: KProperty<*>
): ReadWriteProperty<CLASS, PROP> = delegater.WrappingRWProperty(prop, value, converter)
}
interface DelegateProvider<CLASS, PROP> {
operator fun provideDelegate(
thisRef: CLASS,
prop: KProperty<*>
): ReadWriteProperty<CLASS, PROP>
}
interface IObservable<CLASS> {
val manager: ObservableManager<CLASS>
}
open class Observable<CLASS> : IObservable<CLASS> {
override val manager = ObservableManager<CLASS>()
}
@Suppress("UNCHECKED_CAST")
inline fun <reified CLASS, PROP> IObservable<CLASS>.observable(value: PROP): DelegateProvider<CLASS, PROP> = ObservableValue(manager, value) { a: Any? -> a as PROP }
inline fun <CLASS, reified PROP> IObservable<CLASS>.registerListener(property: KProperty1<CLASS, PROP>, crossinline consumer: BiConsumer<PROP>) = manager.registerListener(property, consumer)
class Test : IObservable<Test> by Observable() {
var test by observable("old")
}
fun main(args: Array<String>) {
val test = Test()
test.registerListener(Test::test) { old, new ->
print("test: $old -> $new")
}
test.test = "new" // prints "test: old -> new"
}