125

Firebase's snapshot.getValue() expects to be called as follows:

snapshot?.getValue(Person::class.java)

However I would like to substitute Person with a generic parameter that is passed into the class via the class declaration i.e.

class DataQuery<T : IModel> 

and use that generic parameter to do something like this:

snapshot?.getValue(T::class.java)

but when I attempt that I get an error stating that

only classes can be used on the left-hand side of a class literal

Is it possible to provide a class constraint on the generic parameter like in C# or is there some other syntax I can use to get the type info for the generic param?

2
  • can you provide more context to your question, as a function with type parameter versus a class with type parameter would have two different answers. You accepted a link-only answer which covered one of those cases. Commented Dec 26, 2015 at 17:05
  • Your question should show the full context of the code causing the error. In some cases your code would be correct (if we imagine it is in an inline function with reified type), in other cases not. And there are two cases the generics could be but you don't make it clear. Commented Dec 28, 2015 at 0:20

6 Answers 6

124

For a class with generic parameter T, you cannot do this because you have no type information for T since the JVM erases the type information. Therefore code like this cannot work:

class Storage<T: Any> {
    val snapshot: Snapshot? = ...

    fun retrieveSomething(): T? {
        return snapshot?.getValue(T::class.java) // ERROR "only classes can be used..."
    }
}

But, you can make this work if the type of T is reified and used within an inline function:

class Storage {
    val snapshot: Snapshot? = ...

    inline fun <reified T: Any> retrieveSomething(): T? {
        return snapshot?.getValue(T::class.java)
    }
}

Note that the inline function if public can only access public members of the class. But you can have two variants of the function, one that receives a class parameter which is not inline and accesses private internals, and another inline helper function that does the reification from the inferred type parameter:

class Storage {
    private val snapshot: Snapshot? = ...

    fun <T: Any> retrieveSomething(ofClass: Class<T>): T? {
        return snapshot?.getValue(ofClass)
    }
    
    inline fun <reified T: Any> retrieveSomething(): T? {
        return retrieveSomething(T::class.java)
    }
}

You can also use KClass instead of Class so that callers that are Kotlin-only can just use MyClass::class instead of MyClass::class.java

If you want the class to cooperate with the inline method on the generics (meaning that class Storage only stores objects of type T):

class Storage <T: Any> {
    val snapshot: Snapshot? = ...

    inline fun <reified R: T> retrieveSomething(): R? {
        return snapshot?.getValue(R::class.java)
    }
}

The link to reified types in inline functions: https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters

Sign up to request clarification or add additional context in comments.

4 Comments

I have class abstract class ViewModelFragment<T : ViewModel> : Fragment() and method inside ViewModelProviders .of(somethingNotInteresting) .get(getViewModelClass()) giving T.class:java from your code inline fun <reified R: T> getViewModelClass(): Class<R> = R::class.java, but Compiler complains that "Cannot use 'T' as reifined type. Use Class instead". Do you know how to fix it?
private inline fun <reified T> className() = T::class.java private fun initFragmentCommunication() { fragmentEventViewModel = ViewModelProviders.of(this, viewModelFactory).get(className<ActivityFragmentViewModel<ActivityFragmentEvents, String?>>()) }
retrieveSomething can't be called from java though
@anvarik correct, I was not confining my answer to a requirement that it be callable from Java since it was a Kotlin only question.
30

What you need is reified modifier for your generic param, you can read about it here. https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters So if you do something like that:

inline fun <reified T : Any>T.logTag() = T::class.java.simpleName

you will get name of the actual caller class, not "Object".

3 Comments

Don't forget to declare your type parameter as T : Any, otherwise it will be nullable.
Also, don't forget that the function has to be declared inline.
Thats actially doesnt work. You will receive not generic parameter class.
22

you can get its class type like this

snapshot?.getValue((this.javaClass
                        .genericSuperclass as ParameterizedType)
                        .actualTypeArguments[0] as Class<T>)

2 Comments

getting error: java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
@SachidanandaSahu probably you are trying to cast the class itself (this.javaClass as ParameterizedType) or may be you have some inheritance
7

Using typeOf is another option. (Added in Kotlin 1.3)

Example:

@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> someMethod(data: T) {

    val typeClassifier = typeOf<T>().classifier

    if (typeClassifier == List::class) {
        //Do something
    }

}

1 Comment

This is actually a better option, as it also gives all the details about parameterized types of T, like if T is Map<String, List<Int>>, you can find out the entire thing - even the Int!
3
  1. Using class comparison. See https://stackoverflow.com/a/61756761/2914140.

     inline fun <reified T> SharedPreferences.getData(key: String, defValue: Any? = null): T? =
         when (T::class) {
             Integer::class -> {
                 val value = getInt(key, (defValue as? Int) ?: 0) as T
                 if (value == 0) defValue as? T else value
             }
             Long::class -> {
                 val value = getLong(key, (defValue as? Long) ?: 0L) as T
                 if (value == 0) defValue as? T else value
             }
             Boolean::class -> {
                 val value = getBoolean(key, (defValue as? Boolean) ?: false) as T
                 if (value == false) defValue as? T else value
             }
             String::class -> getString(key, defValue as? String) as T?
             else -> throw IllegalStateException("Unsupported type")
         }
    
  2. Using isAssignableFrom. See https://dev.to/cjbrooks12/kotlin-reified-generics-explained-3mie.

     inline fun <reified T : Number> SharedPreferences.getData(key: String): T? {
         val cls = T::class.java
         return if (cls.isAssignableFrom(Integer::class.java)) {
             getInt(key, 0) as T
         } else if (cls.isAssignableFrom(Long::class.java)) {
             getLong(key, 0) as T
         } else {
             throw IllegalStateException("Unsupported type")
         }
     }
    

For Double in SharedPreferences see https://stackoverflow.com/a/45412036/2914140.

Use:

val s: String? = preferences.getData("key", "")
val i = preferences.getData<Int>("key")

Comments

-2

This is what I have.

class ClubsViewModel<T>(clazz: Class<T>) :  BaseViewModel<T>(clazz) {
    private var _mClubs = MutableLiveData<List<T>>()

     listenToFireStoreCollection("Clubs", _mClubs)
...
}

class BViewModel<T>(clazz: Class<T>) :  BaseViewModel<T>(clazz) { 
  
    private var _mBs = MutableLiveData<List<T>>()
    listenToFireStoreCollection("Bname", _mBs)
...
}

class BaseViewModel<T>(val clazz: Class<T>) {
    protected val mFirestore = Firebase.firestore

    protected  fun listenToFireStoreCollection(val collectionName: String, liveData: MutableLiveData<List<T>>) 
        mFirestore.collection(collectionName).addSnapshotListener { snapshot, e ->
            if (e != null) {
                return@addSnapshotListener
            }
            if (snapshot != null) {
                liveData.value = snapshot.documents.mapNotNull { it.toObject(clazz) }
            }
        }
    }
}
//FRAGMENT EXAMPLES.
class ClubsFragment : Fragment() {

    private val mClubsViewModel: ClubsViewModel<ClubsFSEntity> by viewModels()
...
}

class BsFragment : Fragment() {

    private val mBsViewModel: BsViewModel<BsFSEntity> by viewModels()
...
}

I have a similar problem here: Hilt Dependency injection with Class<T> in ViewModel

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.