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