If a generic container type is written in Java (such as LiveData<T>), it has to be used safely xor elegantly.
To use it safely and elegantly, declare the type in Kotlin.
I had an interesting conversation with my colleague today about the nullability of the input of an Observer on the LiveData. Since our app has been adopting Android Architecture Components, we have widely used LiveData and Observer across our codebase, as shown in the following scaffold:
This example probably looks too familiar to you. But here is the question: What’s the type of the lambda parameter profile at line 8?
The answer in Kotlin is a platform type: Profile! while the answer in Java is Profile. To handle the nullability and take Kotlin-interoperability into consideration, nullness annotation is recommended for Java code. So the polished answer could be @Nullable Profile or @NonNull Profile.
To make sure our code runs safely, we can always assume that profile could be null. A simple null check would suffice, either in Java or Kotlin.
In a real-world production code though, the null checks quickly grow into an exponential problem. A page may allow the user to paginate, also model repository response as a Resource with loading, success, and error states, we may end up using a type like LiveData<Resource<PagingList<Profile>>>, to represent the search result of people.
Now the question is how to handle the nullness of the type above: LiveData may hold a null Resource, Resource may hold a null PagingList, PagingList may hold nulls in the item list. Our NPath complexity can grow exponentially! 🤯
When I inspected our codebase, I found 5 different teams writing different utility classes, as their attempts handle such nullness in a seemingly standardized way. Nonetheless, among these utility classes, there are always subtle differences so that they cannot be unified easily without behavioral change. The caller code looks fragmented and not elegant, and people need to learn to use the correct utility when working across the app.
Can we write elegant code, which does not perform null checks and optimistically assumes that the value is always non-null? The answer would be a conservative yes unless we find evil writers invokes setValue(null)
It is nearly impossible for us to exhaustively confirm that there are no such invocations setValue(null). We might be able to search through our codebase, but it is not always feasible to search for all the libraries that we are depending on.
The conclusion arrives that if the generic container type is defined in Java, like LiveData or PagingList, we recommend performing null checks to write defensive and safe code.
The silver lining
Kotlin is our silver lining to this nullness dilemma. We can declare the generic container type as the following:
With the declaration above, we can now enjoy the null elegance with null-safe Kotlin compiler! 🎉
In case we do want to allow setValue(null) for semantic reasons, we could declare LiveData2<String?> to inform all the observers that null need to be handled.
We are still working towards Kotlin adoption for our app, however, I do not have high confidence that Android Team at Google will rewrite LiveData in Kotlin within the next two years.
I would like to thank my colleague Curtis for inspiring this discussion and sharing his insights.