Почему переменные не создаются при ссылке из OnSelectionChanged через пользовательский текст редактирования?

#android #kotlin

#Android #kotlin

Вопрос:

Все переменные, на которые ссылаются, onSelectionChanged являются нулевыми в соответствии с отладчиком, и если я запускаю свой образец приложения со следующим добавлением в простой макет, мое приложение вылетает с помощью nullpointer .

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

Простейший пример пользовательского 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()
    }
}
  

с помощью трассировки стека

      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)
  

Итак, вопрос в том, почему переменная a равна нулю? Он должен был быть создан при первой ссылке на него в onSelectionChanged независимо от того, как DummyEditText создается экземпляр. Чего мне здесь не хватает?

Любые указания на то, что происходит, приветствуются!

Комментарии:

1. Я могу лишь частично воспроизвести вашу проблему — я получаю сообщение «Ошибка раздувания класса com.x.y.DummyEditText» до того, как получил NPE, это относится и к вам? Мне нужно было добавить super.onSelectionChanged(selStart, selEnd) , чтобы код вообще компилировался

2. Да, для меня это тоже так, что главная ошибка — это описание инфляции, но когда вы смотрите дальше, вы видите, что это вызвано NPE. Странно, что вы не смогли заставить его скомпилироваться без суперзвонка. В моем случае такой проблемы нет.

Ответ №1:

При отладке я обнаружил, что onSelectionChanged это вызывается перед запуском init блока.

Как упоминалось в документации:

Во время инициализации экземпляра блоки инициализатора выполняются в том же порядке, в котором они отображаются в теле класса, чередуясь с инициализаторами свойств

Свойства не были инициализированы при OnSelectionChanged вызове.

Итак, я полагаю, что происходит то, что в одном из суперклассов какой-то код в конструкторе должен вызывать onSelectionChanged вызов. Итак, перед инициализацией любого из свойств onSelectionChanged вызывается is, и поэтому ваше поле равно null.

Вот пример для репликации сценария:

 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
    }
}
  

Комментарии:

1. Ах да, это имеет большой смысл. Также простой источник ошибок, поскольку детали реализации ОС могут распространяться, поэтому потенциально могут повлиять на стабильность приложения. Тем более, что реализация меняется на разных версиях Android и устройствах. Спасибо, что уделили время ответу и очень полезной демонстрации проблемы.