How to get class of generic type parameter in Kotlin
Asked Answered
W

3

9

I would like get the class property from a generic type T. I've decided to extend to Any but I'm getting an error. https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/index.html#extension-properties

I have the following code:

class FirebaseDBRepo<T : Any>(val child:String) {

private var callback: FirebaseDatabaseRepositoryCallback<T>? = null
private val ref: DatabaseReference
private val listener = object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {

        //T::class.java is showing the error cannot use t as reified type parameter use class instead

        val gameDS = dataSnapshot.getValue(T::class.java)
        callback!!.onSuccess(gameDS!!)
    }

    override fun onCancelled(databaseError: DatabaseError) {

    }
}

init {
    ref = FirebaseDatabase.getInstance().reference.child(child)
}


fun addListener(callback: FirebaseDatabaseRepositoryCallback<T>) {
    this.callback = callback
    ref.addValueEventListener(listener)
}

fun removeListener() {
    ref.removeEventListener(listener)
}

}
Worth answered 21/9, 2018 at 15:29 Comment(0)
D
16

You can only get the class on reified variables. The same thing happens in java, but with a slightly different message:

public <T> void x(){
    T t = T.class.newInstance();
}

In Java, you'd solve this like:

public <T> void x(Class<T> cls){
    T t = cls.newInstance();
}

The same applies to Kotlin, and any calls. You'd need to get a class instance in most cases. However, Kotlin supports reified generics using a keyword, but only on inline generic functions. You could pass a class, but in functions, it's really easy just using the reified keyword.

As in you can't declare a class with reified generics, which means this is invalid:

class SomeClass<reified T>

But it is valid for inline functions, meaning you can do:

inline fun <reified T> someFunction()

So you have two options. But since you extend a listener, the first option of adding the generics to the function isn't an option. You can't override a non-generic method with generics. It won't compile.

Which leaves the second option, which unfortunately is rather hackish; passing the class to the constructor. So it should look like this:

class FirebaseDBRepo<T : Any>(val child: String, private val cls: Class<T>) {

Now, I don't use Firebase, so I have no clue what classes you'd pass, so for this next example, I just use String.

Kotlin supports some type minimization without going over to raw types. This:

val t = FirebaseDBRepo<String>("", String::class.java)

Could be shortened to this:

val t = FirebaseDBRepo("", String::class.java)

The inferred type in both cases is FirebaseDBRepo<String>.

Dieppe answered 21/9, 2018 at 15:52 Comment(1)
I would love it if Kotlin could figure out what to set cls to if I used val t = FirebaseDBRepo<String>(""). Especially because sometimes I have a class definition that's more like class FirebaseDBRepo<T : Any>(val child: T, private val cls: Class<T>). Kotlin can figure out T from child. In that situation I want to be able to do FirebaseDBRepo("").Fredfreda
F
10

Since you are running on the JVM, type erasure is a thing. This means (in simplified terms), that during compilation, the generics are simply ignored. Therefore, you cannot get the class of T, as the JVM doesn't even know what you mean by "T".

Kotlin uses a clever trick to come around this limitation in some cases. When you are using inline functions, the compiler does not call the function you defined, but instead, copies the whole body to the location where you called it. This can only be done for inline functions. Not classes.

There is a workaround tough: Just add private val classT: Class<T> to the constructor and use the parameter instead!

Flouncing answered 21/9, 2018 at 15:50 Comment(0)
C
0

Maybe it is too late but you could get the memory address from the generic class.

try to use:

object: GenericTypeIndicator<"T>() {}

to get the memory address from ur generic value.

It looks then so:

val gameDS = dataSnapshot.getValue(object: GenericTypeIndicator<"T">(){}

But you need to give your genericType without the ""

Maybe it is a solution for you.

Chrissy answered 13/4, 2022 at 13:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.