Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confused about when Kotlin infers a platform type

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?

like image 310
Alan O'Donnell Avatar asked Nov 18 '25 02:11

Alan O'Donnell


1 Answers

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.

like image 183
Rob Pridham Avatar answered Nov 20 '25 07:11

Rob Pridham