Search code examples
kotlingenericskotlin-reified-type-parameterskotlin-inline-class

Cannot use 'T' as reified type parameter even I declared that T should be any class inherited by Parcelable


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)
            }
        }
    }

}

Solution

  • 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)
                }
            }
        }
    
    }