Kotlin Non-Nullable Pitfall
One of the first things you’ll encounter when starting to code with Kotlin is the topic of null safety — especially nullable types and non-null
types. The Kotlin documentation states the following about non-nullable values:
“In Kotlin, the type system distinguishes between references that can hold
null
(nullable references) and those that cannot (non-nullable references). For example, a regular variable of typeString
cannot holdnull
.”
The Non-Nullable Null Value
According to the above, variables of a non-nullable type are never null
. But is this really true? In general, yes, but just recently, I stumbled over an exception to this paradigm.
I worked on a feature that involved text editing on Android — in particular, reacting to changes of the cursor position.
To achieve that, I derived a new class — CustomEditText
— from Android’s AppCompatEditText
:
CustomEditText(context: Context, private val listener: CustomListener): AppCompatEditText(context) { override fun onSelectionChanged(selStart: Int, selEnd: Int) { super.onSelectionChanged(selStart, selEnd) listener.onSelectionChange(selStart, selEnd) } }
As you can see, there’s a private non-nullable property, listener
, which must be passed in the constructor. We should be good to access this property at any given time.
But instantiating a CustomEditText
causes this error message:
java.lang.NullPointerException: Attempt to invoke interface method 'super.onSelectionChanged(int, int)' on a null object reference
Apparently listener
was null
. What happened?
The culprit is one of the base class constructors — TextView
, to be specific — which calls onSelectionChange()
under the hood.
So here’s a bit more detail: In Kotlin, if you try to access a non-null
value in an overridden method during the execution of the base class constructor, you’ll end up with a NullPointerException
.
This is because the base class constructor is executed before the subclass constructor, and if the base class constructor calls an overridden method of the subclass, the constructor of the latter hasn’t even run, yet — and its members are not initialized.
If possible, avoid calling overridable methods from a constructor.
In our case, avoiding overridable methods isn’t not an option, because Android’s TextEdit
class already calls onSelectionChanged
from its constructor.
So, the obvious way to solve the problem is using Kotlin’s safe-call operator, ?.
:
override fun onSelectionChanged(selStart: Int, selEnd: Int) { super.onSelectionChanged(selStart, selEnd) listener?.onSelectionChange(selStart, selEnd) }
Unfortunately, in my case, this wasn’t enough. Here at PSPDFkit, we have very strict linter rules, which deem this change unacceptable, because “why use a safe-call if the value can never be null?” As a result, the compiler emits an “unnecessary safe-call” compiler error.
There are two options to address this. The first is to suppress the warning:
override fun onSelectionChanged(selStart: Int, selEnd: Int) { super.onSelectionChanged(selStart, selEnd) @Suppress("UNNECESSARY_SAFE_CALL") listener?.onSelectionChange(selStart, selEnd) }
The second is to introduce a function that takes a nullable parameter and hence works around the linter rule:
override fun onSelectionChanged(selStart: Int, selEnd: Int) { super.onSelectionChanged(selStart, selEnd) handleOnSelectionChange(selStart, selEnd) } private fun handleOnSelectionChange(selStart: Int, selEnd: Int) { listener?.onSelectionChange(selStart, selEnd) }
I chose the second option, because I tend to avoid @Supress
commands. However, which option you choose doesn’t make much of a difference; it’s simply a matter of preference.
Conclusion
Generally it can be said non-nullable types behave as expected, with the exception of when constructors call an overridable method. To be safe, be sure to check base class constructors.
Michael has a thing for architecture (slick software and unconventional buildings). If he’s not sitting in front of a computer or watching a series, you’ll find him in a CrossFit gym, in the kitchen, or at some Burning Man event.