I’m currently working towards a goal I believe to be attainable – having the GitHub line show me that 100% of my project is Kotlin (but please ignore my 0.1% Ruby content, that’s from Fastlane, I swear I have nothing to do with it.) Rather than me gushing about Kotlin and giving this thing I discovered a 5 paragraph long intro, let’s just get into it.

The API I’m working against has a few types that I have to write custom adapters for (using Retrofit and GSON, a fairly common combination.) The API returns null values for some Dates and some Doubles. The problem we’ll be focusing in on is the DoubleAdapter.

private static class DoubleAdapter extends TypeAdapter<Double> {
        public Double read(JsonReader reader) throws IOException {
            if (reader.peek() == JsonToken.NULL) {
                reader.nextNull();
                return Double.NaN;
            }
            return reader.nextDouble();
        }
        public void write(JsonWriter writer, Double value) throws IOException {
            if (value.isNaN()) {
                writer.nullValue();
            } else {
                writer.value(value);
            }
        }
}

Fairly straightforward – if the JsonReader finds the value expected as Double to be null, we will instead return the Double.NaN value. So, we convert this to Kotlin, and we get this.

class DoubleAdapter : TypeAdapter<Double>() {
        override fun read(reader: JsonReader): Double {
            if (reader.peek() == JsonToken.NULL) {
                reader.nextNull()
                return Double.NaN
            }
            return reader.nextDouble()
        }

        override fun write(writer: JsonWriter, value: Double) {
            if (value.isNaN()) {
                writer.nullValue()
            } else {
                writer.value(value)
            }
        }
    }

But now, after doing these conversions, suddenly my non-null Doubles are coming through as null, which of course means app crashes.

The Struggle: What’s going on?

While I’ve never run into this issue before, I started immediately going off the assumption that Double in Kotlin would not be identical to Double in Java, but I didn’t know how exactly they would be different. Currently, my type adapter was being registered as such:

GsonBuilder()
  .registerTypeAdapter(Double::class.java, DoubleAdapter())
  .build()

Everything looked fine, but every instance of Double that I was using was coming from Kotlin, not from Java, and Android Studio wasn’t making it easy to fully qualify I wanted the Java object specifically. As I probably should have done first thing, I went to the source, and read the documentation on how these Kotlin objects work.

Kotlin treats some Java types specially. Such types are not loaded from Java “as is”, but are mapped to corresponding Kotlin types.

double -> kotlin.Double

Java’s boxed primitive types are mapped to nullable Kotlin types:

java.lang.Double -> kotlin.Double?

Interesting. Now we’re getting somewhere. So, if I want to force the usage of the boxed primitive Double from Java, I have to tell Kotlin that I am using the nullable version of Double. Seems pretty easy, except Kotlin formatting will not allow you to do that as a class reference. I’ll show an example of what Kotlin is telling us in this article to do to use the correct Java type, which doesn’t work.

GsonBuilder()
  .registerTypeAdapter(Double?::class.java, DoubleAdapter())
  .build()

As Kotlin says in the guide, this is a mapped reference to Java’s boxed primitive Double, but this does not compile, as type in a class literal must not be nullable. So, the only solution to this is:

GsonBuilder()
  .registerTypeAdapter(java.lang.Double::class.java, DoubleAdapter())
  .build()

Forcing a reference to the Java class. Voila, everything’s working great, and we’re at 100% Kotlin for this module!

But, does this really count? I feel a little cheated by this solution, and a little bit by how these mapped objects are designed to work in the first place. If the auto-generated mapping doesn’t work 100% of the time, maybe it’s broken? I’m not sure of a better solution, or that this issue is even bad enough to make Kotlin rethink how they are mapping their objects, but it made me think long enough to write this.