Messing around with the following example code made me realize I'm confused about when Kotlin infers a platform type:
inline fun <reified U, V> LiveData<U>.map(crossinline f: (U) -> V): LiveData<V> {
val result = MediatorLiveData<V>()
result.addSource(this) { u -> // u: U!
if (u is U) {
result.value = f(u)
}
}
return result
}
val x = MutableLiveData<Int>()
val y = x.map { it + 1 }
x.value = 1 // ok
x.value = null // will eventually crash from doing null + 1
My hope was that this use of reified+crossinline would prevent the map function from calling f when u is null, because it would check if null is Int. Interestingly though, the null sneaks past the check.
I thought I must be misunderstanding how reified+crossinline works, but it seems that what's actually at issue is type inference: U seems to be inferred as Int!, not Int. For example, manually specifying map's type parameters gets things to work:
val y = x.map<Int, Int> { it + 1 }
So. Why was Kotlin infering a platform type above? (After all, x has inferred type MutableLiveData<Int>, not MutableLiveData<Int!>.) Is there a way to not infer a platform type, aside from doing the inference yourself?
The first question you should ask, before you go anywhere near Kotlin's complexities like reified generics, is: why can I do this?
val x = MutableLiveData<Int>()
x.value = null
and the answer to that is rooted in the fact that MutableLiveData is written in Java, not Kotlin.
If you wrote this class in Kotlin:
class MyKotlinClass<T: Any> {
lateinit var value: T
}
and then tried to do this:
val x = MyKotlinClass<Int>()
x.value = null
then it wouldn't compile. That's because you've defined the type as the non-nullable Any.
But imagine writing such a generic class in Java, where Any is Object and you can't define nullability on anything really, except by providing annotations:
class ExampleJavaClass<T> {
public ExampleJavaClass(T param) {
}
static ExampleJavaClass getStringInstance() {
return new ExampleJavaClass<String>(null);
}
}
That's allowed. So to permit fully compatible interop with Java, Kotlin has to assume that nulls are OK. Unfortunately for you, it's invisible.
Until someone produces a Kotlin-specific implementation of LiveData that will tighten this up, you have to deal with null, or you can write one yourself, like this. I wouldn't necessarily encourage writing your own - there may be adverse consequences.
In many cases, if you own the Java code, you can improve the interop situation by annotating the Java to define nullability, e.g. @NonNull. However it doesn't look like you can do this using the existing annotations on type parameters.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With