Kotlin 协程:androidx.lifecycle.MutableLiveData 对象出现 NullPointerException
Kotlin Coroutines: NullPointerException on androidx.lifecycle.MutableLiveData object
我正在尝试使用 kotlin 协程通过 androidx.room 工件从数据库中获取数据。我已经分析了代码,但我还没有找到解决问题的方法。
我在已设置为可为空的 tonight
对象上收到空异常。我不应该在可为 null 的对象上得到 null 异常。
这是 ViewModel class 我正在从中编写用于获取数据的逻辑
SleepTrackerViewModel.kt
package com.google.samples.apps.trackmysleepquality.sleeptracker
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.viewModelScope
import com.google.samples.apps.trackmysleepquality.database.SleepDatabaseDao
import com.google.samples.apps.trackmysleepquality.database.SleepNight
import com.google.samples.apps.trackmysleepquality.formatNights
import kotlinx.coroutines.launch
/**
* ViewModel for SleepTrackerFragment.
*/
class SleepTrackerViewModel(
val database: SleepDatabaseDao,
application: Application
) : AndroidViewModel(application) {
init {
initializeTonight()
}
/**
* [tonight] is the object that holds the most recent [SleepNight]
*/
private var tonight = MutableLiveData<SleepNight?>()
/**
* Get all the nights from the database
*/
private val nights = database.getAllNights()
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
private fun initializeTonight() {
viewModelScope.launch {
tonight.value = getTonightFromDatabase()
}
}
private suspend fun getTonightFromDatabase(): SleepNight? {
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
// If the start and end times are not the same, meaning that the night has already been completed
night = null
}
return night
}
/**
* Function to start tracking a new SleepNight
*/
fun onStartTracking() {
viewModelScope.launch {
val newNight = SleepNight()
insert(newNight)
//assign newNight to tonight as the most recent SleepNight
tonight.value = getTonightFromDatabase()
}
}
private suspend fun insert(night: SleepNight) {
database.insert(night)
}
fun onStopTracking() {
viewModelScope.launch {
val oldNight = tonight.value ?: return@launch
oldNight.endTimeMilli = System.currentTimeMillis()
update(oldNight)
}
}
private suspend fun update(night: SleepNight) {
database.update(night)
}
fun onClear() {
viewModelScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
database.clear()
}
}
错误信息
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.google.samples.apps.trackmysleepquality, PID: 21352
java.lang.NullPointerException: Attempt to invoke virtual method 'void
androidx.lifecycle.MutableLiveData.setValue(java.lang.Object)' on a null
object reference
at
com.google.samples.apps.trackmysleepquality.sleeptracker.SleepTrack
erViewModel$initializeTonight.invokeSuspend(SleepTrackerViewMod
el.kt:56)
at
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(Contin
uationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
这是您在 Kotlin 中获得 NullPointerException 的几种意想不到的方法之一。在初始化 tonight
之前,您有一个 init
块调用函数 initializeTonight()
。当一个class被实例化时,所有的属性初始化和init
块被按从上到下的顺序调用。
你可能认为它是安全的,因为 tonight
的值是在协程内部设置的,但默认情况下 viewModelScope
同步启动启动协程的 运行 部分,因为它使用 Dispatchers.Main.immediate
。您的 getTonightFromDatabase()
也在调用挂起函数,但该数据库也可能正在使用 Dispatchers.Main.immediate
并且能够在不实际挂起的情况下返回结果。
我会按如下方式更改您的代码。删除 init
块和 initializeTonight
函数。像这样声明 tonight
:
private var tonight = MutableLiveData<SleepNight?>().also {
viewModelScope.launch {
it.value = getTonightFromDatabase()
}
}
另外,我会把它设为 val
而不是 var
。不应该有替换它的理由,因此将其设置为 var
很容易出错。
@Tenfour04 涵盖了导致它的问题,但我只是想指出您错误地阅读了错误消息,它可能向您发送了错误的方向:
java.lang.NullPointerException: Attempt to invoke virtual method 'void
androidx.lifecycle.MutableLiveData.setValue(java.lang.Object)' on a null
object reference
这意味着您正在尝试在 null
对象上调用 setValue
- 即 null.setValue(whatever)
。这不是你的 MutableLiveData
contents 是空的(正如你所说,该值具有可空类型,所以应该没问题) - 它是 MutableLiveData
本身,它就是你所说的 setValue
on.
您在 Kotlin 中不会经常看到这种情况(因为它会进行自己的 null 检查并抛出不同的“oi this shouldn't be null”消息)但它可能会发生!
我正在尝试使用 kotlin 协程通过 androidx.room 工件从数据库中获取数据。我已经分析了代码,但我还没有找到解决问题的方法。
我在已设置为可为空的 tonight
对象上收到空异常。我不应该在可为 null 的对象上得到 null 异常。
这是 ViewModel class 我正在从中编写用于获取数据的逻辑
SleepTrackerViewModel.kt
package com.google.samples.apps.trackmysleepquality.sleeptracker
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.viewModelScope
import com.google.samples.apps.trackmysleepquality.database.SleepDatabaseDao
import com.google.samples.apps.trackmysleepquality.database.SleepNight
import com.google.samples.apps.trackmysleepquality.formatNights
import kotlinx.coroutines.launch
/**
* ViewModel for SleepTrackerFragment.
*/
class SleepTrackerViewModel(
val database: SleepDatabaseDao,
application: Application
) : AndroidViewModel(application) {
init {
initializeTonight()
}
/**
* [tonight] is the object that holds the most recent [SleepNight]
*/
private var tonight = MutableLiveData<SleepNight?>()
/**
* Get all the nights from the database
*/
private val nights = database.getAllNights()
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
private fun initializeTonight() {
viewModelScope.launch {
tonight.value = getTonightFromDatabase()
}
}
private suspend fun getTonightFromDatabase(): SleepNight? {
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
// If the start and end times are not the same, meaning that the night has already been completed
night = null
}
return night
}
/**
* Function to start tracking a new SleepNight
*/
fun onStartTracking() {
viewModelScope.launch {
val newNight = SleepNight()
insert(newNight)
//assign newNight to tonight as the most recent SleepNight
tonight.value = getTonightFromDatabase()
}
}
private suspend fun insert(night: SleepNight) {
database.insert(night)
}
fun onStopTracking() {
viewModelScope.launch {
val oldNight = tonight.value ?: return@launch
oldNight.endTimeMilli = System.currentTimeMillis()
update(oldNight)
}
}
private suspend fun update(night: SleepNight) {
database.update(night)
}
fun onClear() {
viewModelScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
database.clear()
}
}
错误信息
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.google.samples.apps.trackmysleepquality, PID: 21352
java.lang.NullPointerException: Attempt to invoke virtual method 'void
androidx.lifecycle.MutableLiveData.setValue(java.lang.Object)' on a null
object reference
at
com.google.samples.apps.trackmysleepquality.sleeptracker.SleepTrack
erViewModel$initializeTonight.invokeSuspend(SleepTrackerViewMod
el.kt:56)
at
kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(Contin
uationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
这是您在 Kotlin 中获得 NullPointerException 的几种意想不到的方法之一。在初始化 tonight
之前,您有一个 init
块调用函数 initializeTonight()
。当一个class被实例化时,所有的属性初始化和init
块被按从上到下的顺序调用。
你可能认为它是安全的,因为 tonight
的值是在协程内部设置的,但默认情况下 viewModelScope
同步启动启动协程的 运行 部分,因为它使用 Dispatchers.Main.immediate
。您的 getTonightFromDatabase()
也在调用挂起函数,但该数据库也可能正在使用 Dispatchers.Main.immediate
并且能够在不实际挂起的情况下返回结果。
我会按如下方式更改您的代码。删除 init
块和 initializeTonight
函数。像这样声明 tonight
:
private var tonight = MutableLiveData<SleepNight?>().also {
viewModelScope.launch {
it.value = getTonightFromDatabase()
}
}
另外,我会把它设为 val
而不是 var
。不应该有替换它的理由,因此将其设置为 var
很容易出错。
@Tenfour04 涵盖了导致它的问题,但我只是想指出您错误地阅读了错误消息,它可能向您发送了错误的方向:
java.lang.NullPointerException: Attempt to invoke virtual method 'void
androidx.lifecycle.MutableLiveData.setValue(java.lang.Object)' on a null
object reference
这意味着您正在尝试在 null
对象上调用 setValue
- 即 null.setValue(whatever)
。这不是你的 MutableLiveData
contents 是空的(正如你所说,该值具有可空类型,所以应该没问题) - 它是 MutableLiveData
本身,它就是你所说的 setValue
on.
您在 Kotlin 中不会经常看到这种情况(因为它会进行自己的 null 检查并抛出不同的“oi this shouldn't be null”消息)但它可能会发生!