How in Kotlin on Android can a null pointer exception occur in an immutable list during a or each loop?

Viewed 847

I'm getting a RARE null pointer exception in the following code:

class Artist {
  fun draw(canvas: Canvas, state: State){
    state.drawableObjects.forEach { 
      it.draw(canvas) //NULL POINTER!?! Can not call draw(Canvas) on null object
    }
  }
}

class State {
  var drawableObjects = listOf<DrawableObject>()
    set(value) {
      field = value.filter { it.isVisible } // Why not crash here if null?
    }
}

class DrawableObject(val isVisible: Boolean) {
   fun draw(canvas: Canvas) { }
}

How is this possible? The list drawableObjects is immutable. It also does not allow null objects. When the list is changed an entirely new list is set to prevent modification during the draw call.

I should definitely mention that multiple threads are involved. 2 threads only. One calling Artist.draw() and a second calling State.drawableObjects = listOf()

2 Answers

I don't know your data structures, so let's take a simpler case.

Suppose you are trying to populate a List<String> in Kotlin. String is non-nullable, so your Kotlin code will expect that all elements in the List to be not null.

However, it is possible that your List<String> is coming from Java code. In Java, null is perfectly fine to put in a List. And that Java code does not have to be yours — it could be from a library, such as Gson.

So, suppose you have Gson parse this JSON:

[
  "the",
  "quick",
  "brown",
  null,
  "jumped",
  "over",
  "the",
  "lazy",
  null
]

Gson will be happy to parse that into a Kotlin List<String>, and Kotlin's JVM interop will allow it. But, when you start iterating over that List<String>, you will crash with a NullPointerException for the null elements.

Similary, Gson will leave Kotlin properties as null when parsing a JSON object that is missing some of those properties. Suppose you have:

data class Something(foo: Int, bar: Int)

and:

{ foo: 42 }

Gson will happily create a Something instance with bar set to null.

So, my guess, based on your one comment, is that Gson is giving you null values in your List<DrawableObject> based on its parsing rules. You can address this by making that be a List<DrawableObject?> and then doing something about the null elements (filter them out, skip them during your forEach loop, etc.).

I don't believe this is the answer, but I have seen somethign similar in my own project and have gotten around it by using vars and doing the following:

@Suppress("SENSELESS_COMPARISON")
if(myImmutableThatShouldNotBeNull == null){
   myImmutableThatShouldNotBeNull = MyClass()
}

Or in your case:

@Suppress("SENSELESS_COMPARISON")
if(canvas == null){
   phone.throwOffCliff()!!.now()
}
Related