Недавно я столкнулся с NPE, реализующим новый класс Android ViewModel. Исправление проблемы заняло немного больше времени из-за чего-то, чего я до сих пор не понимал в Kotlin.

Объявление значений членов класса после блока init не будет инициализировано при выполнении блока init.

Пример:

class MyClass {
    init {
        updateAnswer("42")
    }
    
    val answer by lazy { MutableLiveData<String>() }
    
    fun updateAnswer(newAnwser: String) {
        answer.value = newAnwser
    }
}

Это вызовет NPE, поскольку делегат answer не был инициализирован в блоке init.

Попытка сделать это:

class MyClass {
    init {
        answer.value = "42"
    }
    
    val answer by lazy { MutableLiveData<String>() }
    
}

будет пойман редактором и компилятором.

Это можно легко исправить, изменив порядок объявления члена данных.

class MyClass {
    val answer by lazy { MutableLiveData<String>() }
    init {
        updateAnswer("42")
    }
    
    fun updateAnswer(newAnwser: String) {
        answer.value = newAnwser
    }
}

На этот раз делегат answer будет установлен в блоке init

Спасибо за прочтение, но я хотел бы отметить, что пример, в котором я поймал это, был более сложным, чем этот упрощенный пример. В моем примере использовались EventBu, о которых я писал в предыдущем посте. Мой пример выглядел примерно так:

class MyClass {
    init {
        EventBus.getDefault().register(this)
    }

    val answer by lazy { MutableLiveData<MyDomainObject>() }

    @Subscribe(sticky = true, threadMode = ThreadMode.BACKGROUND)
    protected fun bind(newAnwser : MyDomainObject) {
        answer.value = newAnwser;
    }
}

Я не осознавал, что к этому времени Eventbus уже имел MyDomainObject и, таким образом, вызывал функцию bind() синхронно с вызовом EventBus.getDefault().register(this).

Обо мне
Я являюсь подрядчиком разработчиков Kotlin/Java для Android. Мне всегда интересно узнать о вашем проекте и о том, чем я могу помочь друг другу. www.wedgetech.co.uk