Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get the name of a Kotlin property?

I have the following function to access a property's delegate. It uses Kotlin reflection to get a property's name and Java reflection to get the field.

fun Any.getDelegate<T>(prop: KProperty<T>): Any {
    return javaClass.getDeclaredField("${prop.name}\$delegate").let {
        it.setAccessible(true)
        it.get(this)
    }
}

The method is used like this:

val delegate = a.getDelegate(A::b)

However, I would prefer to use it like this:

val delegate = a.b.delegate

The problem with the code above is getting the property name of a.b and getting the instance a from a.b. From what I know about Kotlin, this is probably not possible, however I'd like to see if I can clean up my function at all.

To give a bigger picture of what I'm trying do here's my complete code. I want an observable delegate to which I can add and remove observers using the delegate reference and without creating addition variables.

fun Any.addObservable<T>(prop: KProperty<T>, observer: (T) -> Unit) {
    getObservableProperty(prop).observers.add(observer)
}

fun Any.getObservableProperty<T>(prop: KProperty<T>): ObservableProperty<T> {
    return getDelegate(prop) as ObservableProperty<T>
}

fun Any.getDelegate<T>(prop: KProperty<T>): Any {
    return javaClass.getDeclaredField("${prop.name}\$delegate").let {
        it.setAccessible(true)
        it.get(this)
    }
}

class ObservableProperty<T>(
        initialValue: T,
        initialObservers: Array<(T) -> Unit> = emptyArray()) : ReadWriteProperty<Any?, T> {

    private var value = initialValue

    public val observers: MutableSet<(T) -> Unit> = initialObservers.toHashSet()

    public override fun get(thisRef: Any?, desc: PropertyMetadata): T {
        return value
    }

    public override fun set(thisRef: Any?, desc: PropertyMetadata, value: T) {
        this.value = value
        observers.forEach { it(value) }
    }
}

class A() {
    var b by ObservableProperty(0)
}

fun main(args: Array<String>) {
    val a = A()

    a.addObservable(A::b) {
        println("b is now $it")
    }

    a.b = 1
    a.b = 2
    a.b = 3
}

Edit:

I just realized that the function also isn't strict because the property delegate field name is referenced by KProperty name, which doesn't require a strong reference to the enclosing class. Here's an example to demonstrate the problem:

class A() {
    var foo by ObservableProperty(0)
}

class B() {
    var foo by ObservableProperty(0)
}

fun main(args: Array<String>) {
    val a = A()

    a.addObservable(B::foo) {
        println("b is now $it")
    }

    a.foo = 1
    a.foo = 2
    a.foo = 3
}

This compiles and runs without error because A::foo and B::foo both result in a field string of "foo$delegate.

like image 209
Michael Pardo Avatar asked Jul 03 '15 04:07

Michael Pardo


People also ask

What is get () in Kotlin?

In Kotlin, setter is used to set the value of any variable and getter is used to get the value. Getters and Setters are auto-generated in the code. Let's define a property 'name', in a class, 'Company'. The data type of 'name' is String and we shall initialize it with some default value.

What is the :: in Kotlin?

?: takes the right-hand value if the left-hand value is null (the elvis operator). :: creates a member reference or a class reference.

How do I reference a class in Kotlin?

To obtain the reference to a statically known Kotlin class, you can use the class literal syntax: val c = MyClass::class //The reference is a value of type KClass.


1 Answers

Right now reflection is all we can do to get to the delegate object. We are designing a language feature to have direct access to delegate instance, but it's long way to go.

like image 139
Ilya Ryzhenkov Avatar answered Oct 09 '22 05:10

Ilya Ryzhenkov