Abacus
Abacus

Reputation: 19431

How can I set a property of a companion object in Kotlin via reflection?

When I have a class that has a companion object, is it possible to set a property in this companion object using reflection? I can do it with normal properties, but fail on companion objects:

import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.companionObject
import kotlin.reflect.full.memberProperties

class WithProperty {
    lateinit var prop: String

    companion object {
        lateinit var companionProp: String
    }

    fun test() = "$companionProp $prop"
}

fun main(args: Array<String>) {
    val obj = WithProperty()

    val prop = obj::class.memberProperties.filter { it.name == "prop" }.first()
    if (prop is KMutableProperty<*>) {
        prop.setter.call(obj, "world")
    }

    val companion = obj::class.companionObject
    if (companion != null) {
        val companionProp = companion.memberProperties.filter { it.name == "companionProp" }.first()
        if (companionProp is KMutableProperty<*>) {
            companionProp.setter.call(companionProp, "hello") // <-- what must go here as first argument?
        }
    }

    println(obj.test())
}

Calling the setter for the normal property works as it should, but when I call companionProp.setter.call(companionProp, "hello") I get

Exception in thread "main" java.lang.IllegalArgumentException: object is not an instance of declaring class

What do I have to pass as first argument to call() to succeed?

Edit: I wrote companionPropas first argument, but that definitely is wrong, I actually tried with the companion object, but that is not working as well.

Upvotes: 4

Views: 8047

Answers (3)

VadzimV
VadzimV

Reputation: 1261

You can solve the same problem using java reflection.

Companion class:

class Example {
    companion object {
        val EXAMPLE_VALUE = "initial"
    }
}

Update a property using java reflection:

val field = Example::class.java.getDeclaredField("EXAMPLE_VALUE")
field.isAccessible = true
field.set(null, "replaced")

Tested with Kotlin 1.5.30 on Android 12:

Log.d("test-companion", Example.EXAMPLE_VALUE) // outputs "replaced"

WARNING: I'm not sure if java reflection is reliable for this case. It assumes some implementation details of Kotlin complier which could change in a future version. But the solution should be fine for a quick workaround. I used it to verify a bug fix on customer side before the next release of my library.

Upvotes: 4

Salem
Salem

Reputation: 14927

object is not an instance of declaring class

Just as in Java, you need to pass the object itself as the first parameter when calling reflective methods.

The first parameter to call should be the companion object, as that is the object whose property you are trying to modify.

You are passing the companion's class object instead of the companion object itself.

A companion object is accessible either via ClassName.Companion, or when using further reflection, through KClass#companionObjectInstance.

companionProp.setter.call(WithProperty.Companion, "hello")
companionProp.setter.call(obj::class.companionObjectInstance, "hello")
companionProp.setter.call(WithProperty::class.companionObjectInstance, "hello")

When run, both variants print hello world as intended.

Keep in mind that Foo.Companion will result in a compile error if the companion object does not exist while the reflective variant will return null instead.

Upvotes: 4

holi-java
holi-java

Reputation: 30686

the first argument is the instance of the declaring class.

you pass a KProperty instance companionProp rather than a companion object instance. However, you can using KClass.companionObjectInstance to obtain the compantion instance. for example:

//a non-static property having a receiver, so it should be a KMutableProperty1 here
//                   v 
if (companionProp is KMutableProperty1<*, *>) {
//  get the companion object instance ---v
    companionProp.setter.call(obj::class.companionObjectInstance, "hello") 
}

Upvotes: 2

Related Questions