How can I get the name of a Kotlin property?
Asked Answered
U

2

7

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.

Unknot answered 3/7, 2015 at 4:1 Comment(0)
A
3

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.

Axiomatic answered 3/7, 2015 at 13:12 Comment(5)
That's interesting. Is there an issue to follow or a spec to read?Crack
No, not anywhere unfortunately. For now, it's a couple of discussion logs and that's it :)Axiomatic
Good to know that you're working on delegate access and that I've done the best I can for now. Thanks.Unknot
@IlyaRyzhenkov Any progress on this?Rolling
@daka reflection was improved to have direct access to a delegate, see kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/…Axiomatic
P
1

This is how you get the name of a Kotlin Property (although only with an instance of the class). This part will be useful to anyone arriving at this question purely based off its title.

class Stuff(val thing: String)

val stuff = Stuff("cool stuff")
val thingFieldName = "${stuff.thing}\$delegate"
// value of thingFieldName is now "thing"

In terms of getting the delegate itself easier, they say you can now do this:

class Foo {
    var bar: String by ReactiveProperty<String>()
}

val foo = Foo()
val bar = foo.bar
val barDelegate = ... // foo.bar$delegate

See ticket.

Payable answered 12/10, 2017 at 17:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.