Home > Software engineering >  Cannot use 'T' as reified type parameter even I declared that T should be any class inheri
Cannot use 'T' as reified type parameter even I declared that T should be any class inheri

Time:10-27

Since I am not yet good in generics I would like to ask:

Question: why I cannot inform the function getResult that the return value from bundle.getParcelableCompat<T>(BUNDLE_KEY) would be type of T (which is any class inheriting by Parcelable)?

I get error:

Cannot use 'T' as reified type parameter. Use a class instead.

What am I mising? I though I passed all needed information what I am trying to achieve.

class MyClass<T : Parcelable>(
    private val key: String,
    private val clazz: Class<T>
) {

    private inline fun <reified T : Parcelable> Bundle.getParcelableCompat(name: String): T? =
        if (Build.VERSION.SDK_INT >= 33) {
            this.getParcelable(name, T::class.java)
        } else {
            this.getParcelable(name)
        }
    
    fun getResult(fragment: Fragment, result: (T) -> Unit) = with(fragment) {
        setFragmentResultListener(requestKey) { request, bundle ->
            if (request == requestKey) {
                val parcelableCompat: T? = bundle.getParcelableCompat<T>(BUNDLE_KEY)
                parcelableCompat?.let(result)
            }
        }
    }

}

CodePudding user response:

when you call bundle.getParcelableCompat<T>(BUNDLE_KEY) inside getResult T (the one delcared by MyClass) has already been erased so the reified parameter does not help you. It's helpful (and mostly correct!) to think about a reified parameter as just saving you the extra argument of T::class, instead the compiler passes it for you.

However you already keep a Class<T> around, there's no need for the extra complexity. The following should work.

class MyClass<T : Parcelable>(
    private val key: String,
    private val clazz: Class<T>
) {

    private inline fun Bundle.getParcelableCompat(name: String): T? =
        if (Build.VERSION.SDK_INT >= 33) {
            this.getParcelable(name, clazz)
        } else {
            this.getParcelable(name)
        }
    
    fun getResult(fragment: Fragment, result: (T) -> Unit) = with(fragment) {
        setFragmentResultListener(requestKey) { request, bundle ->
            if (request == requestKey) {
                val parcelableCompat: T? = bundle.getParcelableCompat(BUNDLE_KEY)
                parcelableCompat?.let(result)
            }
        }
    }
}

CodePudding user response:

As I said in this answer, reified type parameters in Kotlin are not magic. The compiler still needs a runtime-available type at the call site, so that it can inline your getParcelableCompat method.

To see how this does not work, just inline it yourself:

fun getResult(fragment: Fragment, result: (T) -> Unit) = with(fragment) {
    setFragmentResultListener(requestKey) { request, bundle ->
        if (request == requestKey) {
            val parcelableCompat: T? = if (Build.VERSION.SDK_INT >= 33) {
                this.getParcelable(BUNDLE_KEY, T::class.java)
            } else {
                this.getParcelable(BUNDLE_KEY)
            }
            parcelableCompat?.let(result)
        }
    }
}

See how there is T::class.java? You are trying to get the class of a non-reified type parameter, which is not allowed.

In cases where you call getParcelableCompat with a non-type-parameter class, the inlining would come out fine. The T in T::class.java would be replaced with that class you used.

Since the class type parameter T cannot be reified, there is really no point in making getParcelableCompat have a reified type parameter. It can accept a Class<T> instead:

class MyClass<T : Parcelable>(
    private val key: String,
    private val clazz: Class<T>
) {

    private fun <T : Parcelable> Bundle.getParcelableCompat(name: String, clazz: Class<T>): T? =
        if (Build.VERSION.SDK_INT >= 33) {
            this.getParcelable(name, clazz)
        } else {
            this.getParcelable(name)
        }
    
    fun getResult(fragment: Fragment, result: (T) -> Unit) = with(fragment) {
        setFragmentResultListener(requestKey) { request, bundle ->
            if (request == requestKey) {
                val parcelableCompat: T? = bundle.getParcelableCompat(BUNDLE_KEY, clazz)
                parcelableCompat?.let(result)
            }
        }
    }

}

Alternatively, don't make getParcelableCompat generic at all, since you can use the type parameter T from MyClass

class MyClass<T : Parcelable>(
    private val key: String,
    private val clazz: Class<T>
) {

    private fun Bundle.getParcelableCompat(name: String): T? =
        if (Build.VERSION.SDK_INT >= 33) {
            this.getParcelable(name, clazz)
        } else {
            this.getParcelable(name)
        }
    
    fun getResult(fragment: Fragment, result: (T) -> Unit) = with(fragment) {
        setFragmentResultListener(requestKey) { request, bundle ->
            if (request == requestKey) {
                val parcelableCompat: T? = bundle.getParcelableCompat(BUNDLE_KEY)
                parcelableCompat?.let(result)
            }
        }
    }

}
  • Related