Why are variables not instantiated when referenced from onSelectionChanged via a custom edit text?

Viewed 57

Any variables that are referenced in onSelectionChanged are null according to the debugger, and if I run my sample app with the following added to a simple layout my app crashes with a nullpointer.

 <com.dummy.DummyEditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

The simplest example of a custom AppCompatEditText

package com.dummy

import android.content.Context
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatEditText

class DummyEditText : AppCompatEditText {

    constructor(context: Context) : super(context)
    constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet)
    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    private val a = String()
    override fun onSelectionChanged(selStart: Int, selEnd: Int) {
        a.toString()
    }
}

with the stacktrace

     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String java.lang.String.toString()' on a null object reference
        at com.dummy.DummyEditText.onSelectionChanged(DummyEditText.kt:15)
2020-10-05 12:14:25.794 22859-22859/com.dummy E/AndroidRuntime:     at android.widget.TextView.spanChange(TextView.java:10744)
        at android.widget.TextView$ChangeWatcher.onSpanAdded(TextView.java:13600)
        at android.text.SpannableStringBuilder.sendSpanAdded(SpannableStringBuilder.java:1287)
        at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:777)
        at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:676)
        at android.text.Selection.setSelection(Selection.java:96)
        at android.text.Selection.setSelection(Selection.java:78)
        at android.text.Selection.setSelection(Selection.java:153)
        at android.text.method.ArrowKeyMovementMethod.initialize(ArrowKeyMovementMethod.java:312)
        at android.widget.TextView.setText(TextView.java:6299)
        at android.widget.TextView.setText(TextView.java:6139)
        at android.widget.EditText.setText(EditText.java:121)
        at android.widget.TextView.<init>(TextView.java:1642)
        at android.widget.EditText.<init>(EditText.java:87)
        at android.widget.EditText.<init>(EditText.java:83)
        at androidx.appcompat.widget.AppCompatEditText.<init>(AppCompatEditText.java:74)
        at androidx.appcompat.widget.AppCompatEditText.<init>(AppCompatEditText.java:69)
        at com.dummy.DummyEditText.<init>(DummyEditText.kt:10)

So the question is why is the variable a null? It should have been instantiated when it is first referenced in the onSelectionChanged regardless of how DummyEditText is instantiated. What am I missing here?

Any pointers on what is going on are appreciated!

1 Answers

On debugging, I found out that onSelectionChanged is called before the init block is run.

As mentioned in documentation:

During an instance initialization, the initializer blocks are executed in the same order as they appear in the class body, interleaved with the property initializers

The properties haven't been initialized when OnSelectionChanged is called.

So, I believe, what happening is that in one of the superclasses, some code in the constructor must be causing onSelectionChanged to be called. So before any of the properties are initialized the onSelectionChanged is called and your field is therefore null.

Here is a sample to replicate the scenario:

open class Parent {
    init {
        demo()
    }    
    
    open fun demo() {
        print("Base class function called")
    }
}

class Child() : Parent() {
    
    val demoText = "12345"
    override fun demo() {
        super.demo()

        println(demoText.toString()) // This line will throw a NPE as demoText is not yet instantiated
    }
}
Related