Kotlin协程异常:androidx.lifecycle.MutableLiveData对象触发NullPointerException
咱们先直接看错误日志的核心信息:
java.lang.NullPointerException: 尝试对空对象引用调用虚拟方法 'void androidx.lifecycle.MutableLiveData.setValue(java.lang.Object)'
at com.google.samples.apps.trackmysleepquality.sleeptracker.SleepTrackerViewModel$initializeTonight$1.invokeSuspend(SleepTrackerViewModel.kt:56)
你可能会疑惑:明明tonight已经声明为MutableLiveData<SleepNight?>(),怎么还会触发空指针?其实这是个很容易踩的类初始化顺序坑!
问题根源:初始化顺序搞反了
看你ViewModel里的代码结构:
init { initializeTonight() } private var tonight = MutableLiveData<SleepNight?>()
在Kotlin中,类的初始化是严格按照代码书写顺序执行的:
- 先执行主构造函数
- 再按从上到下的顺序执行
init块和属性初始化代码
你的init块写在了tonight属性声明的前面,导致init调用initializeTonight()时,tonight还没被初始化(此时它的值是null)。等到协程里执行tonight.value = getTonightFromDatabase()时,本质是对null对象调用setValue方法,直接触发空指针!
解决方案:调整属性声明顺序
只需要把tonight的属性声明移到init块前面就行,让它在init执行前完成初始化:
class SleepTrackerViewModel( val database: SleepDatabaseDao, application: Application ) : AndroidViewModel(application) { /** * [tonight] is the object that holds the most recent [SleepNight] */ private var tonight = MutableLiveData<SleepNight?>() init { initializeTonight() } // 剩余代码保持不变... }
这样当init块调用initializeTonight()时,tonight已经是一个合法的MutableLiveData实例,协程里给它赋值就不会有空指针问题了。
额外可读性优化建议
顺便看了下getTonightFromDatabase()的逻辑,虽然功能没问题,但可以优化下可读性:
// 原代码 if (night?.endTimeMilli != night?.startTimeMilli) { night = null } // 优化后 if (night != null && night.endTimeMilli != night.startTimeMilli) { night = null }
这样可以避免空安全链式调用带来的歧义,逻辑更直观。
内容的提问来源于stack exchange,提问作者Meggrain

