Blog Post

Kotlin Non-Nullable Pitfall

Michael Kellner
Illustration: 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 type String cannot hold null.”

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.

Information

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.

Author
Michael Kellner Native Engineer

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.

Explore related topics

Share post
Free trial Ready to get started?
Free trial