Kotlin Platform Types, Nullable Annotations and AOSP: A Cautionary Tale

I think it is fair to say that most of us are super happy with how nullability is handled in Kotlin. The overall code that we are writing with nullability in mind is mostly cleaner looking and helps us avoid the pitfalls we saw in Java. Having said that Platform Types are a place in particular that I ran into where I feel like the developer experience and the impact to the user is worse. If you are not aware, Platform Types are types that were created when Kotlin cannot infer the nullability of a type from Java. Let’s look at the URI class in Android as an example, and specifically let’s look at the getQueryParameter method:

public String getQueryParameter(String key) {
  return null;
}

If you will notice the return type in Java is String which can inherently return null as a valid response but Kotlin will not attempt to infer that and instead will mark this type as a Platform Type or String!. Because String! is able to be null we need to immediately treat it accordingly, which means using the safe call operator, ?. .

Now there is another way for us to solve the problem if you have access to source Java code that is returning here. You can add one of six approved annotations that denote nullability, which will tell the compiler how other code should expect to handle the nullability of the return types. The six types used here are the following:

• JetBrains (@Nullable and @NotNull from the org.jetbrains.annotations package)

• Android (com.android.annotations and android.support.annotations)

• JSR-305 (javax.annotation, more details below)

• FindBugs (edu.umd.cs.findbugs.annotations)

• Eclipse (org.eclipse.jdt.annotation)

• Lombok (lombok.NonNull).

My assumption is that this list is just widely popular and/or used annotations in Java. So to fix our problem we could do the following:

@Nullable//If the return type can be null
public String getQueryParameter(String key)

Or

@NonNull//If the return type can NEVER be null
public String getQueryParameter(String key)

Cool... so we’re done here, right? Nope, not at all. We don’t have access to the source code because this is an Android class, so what have they done to tell us the return type's nullability? It turns out that Google has its own internal Annotation to handle nullability which is not on the supported list. And the example class URI that we are using actually uses that Annotation as of Pie.

So, what’s the problem with that? Well, let’s expand the scope of our example. Still using uri.getQueryParameter(...) let’s create our own method that takes the result of that method and consumes it:

fun handleParam(param: String)

As you can see, in this case I am saying that the String type of param should not be null. And let’s now pass it the result of our URI param:

val param = uri.getQueryParameter(“id”) // Get the ID query Param
handleParam(param)

If the result of parsing the query param is null, when we call handleParam(...) with the null value an IllegalStateException is thrown and your app crashes. So basically we have swapped out an NPE for this new exception. What’s worse is that none of this fails at compile time. As far as the compiler is concerned, even though Platform Types are technically nullable, it will not proactively tell us that a possible error exists.

The result is that we need to be very careful about the return types of the Java libraries that we consume. The first and best solution is to find Kotlin based equivalent libraries where Platform Types do not exist. Though this is not a problem I suspect will be around for a long time it is something to keep in mind now and be wary of when interacting with Java code.

https://imnotyourson.com/which-nullness-annotation-you-should-use-on-android/