Android Kotlin MutableLiveData.setValue空对象引用报错排查
空指针报错根因
该报错与Fragment端LiveData观察者的实现逻辑无关,问题出在GameViewModel的Kotlin类属性初始化顺序错误:
报错堆栈明确指向ViewModel实例化阶段:构造方法执行init块、调用
getNextWord()给_currentScrambledWord赋值时,_currentScrambledWord自身还未完成初始化,值为null。
Kotlin类的成员属性初始化、init块执行严格按照代码从上到下的书写顺序执行。如果代码中init块写在了_currentScrambledWord属性声明的前面,那么init块触发getNextWord()时,_currentScrambledWord还没有被赋值为MutableLiveData实例,调用setValue就会触发空指针。
典型错误写法如下:
class GameViewModel : ViewModel() { // 错误:init块写在LiveData属性声明之前 init { getNextWord() } // 执行到上面init块的时候,下面这行还没跑,_currentScrambledWord是null private val _currentScrambledWord = MutableLiveData<String>() val currentScrambledWord: LiveData<String> = _currentScrambledWord private var score = 0 private var wordCount = 0 private fun getNextWord() { // 这里给null对象setValue直接崩溃 _currentScrambledWord.value = generateScrambledWord() } }
修复方案
- 调整
GameViewModel内的代码书写顺序:将所有成员属性(包括Backing Property对应的MutableLiveData、得分、单词计数等状态变量)的声明,全部移动到init块之前,保证init块执行时所有依赖的属性已经完成初始化。
修正后的正确代码结构:
class GameViewModel : ViewModel() { // 第一步:先声明并初始化所有成员属性 private val _currentScrambledWord = MutableLiveData<String>() val currentScrambledWord: LiveData<String> = _currentScrambledWord private var score = 0 private var currentWordCount = 0 private lateinit var currentWord: String private val wordList = mutableListOf<String>() // 第二步:属性全部初始化完成后,再写init块 init { wordList.addAll(listOf("apple", "banana", "orange")) // 示例词库 getNextWord() } private fun getNextWord() { currentWord = wordList.random() val scrambledWord = currentWord.toList().shuffled().joinToString("") // 此时_currentScrambledWord已经完成初始化,赋值不会触发空指针 _currentScrambledWord.value = scrambledWord currentWordCount++ } }
- 额外注意:不要为了规避这个问题给
_currentScrambledWord加lateinit或者可空修饰,保持val不可变修饰+正确书写顺序即可,避免后续出现其他一致性问题。Fragment端基于viewLifecycleOwner绑定观察者的写法符合规范,不需要修改。
内容的提问来源于stack exchange,提问作者Theprogrammingnoob
相关产品推荐
相关产品推荐

