首页 > 解决方案 > Kotlin 协程:androidx.lifecycle.MutableLiveData 对象上的 NullPointerException

问题描述

我正在尝试使用 kotlin Coroutines 使用 androidx.room 工件从数据库中获取数据。我已经分析了代码,但我还没有找到解决问题的方法。

tonight我已经设置为可为空的对象出现空异常。我不应该在可空对象上得到空异常。

这是我正在编写用于获取数据的逻辑的 ViewModel 类

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$1.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)

标签: androidkotlinkotlin-coroutines

解决方案


这是在 Kotlin 中获得 NullPointerException 的几种意想不到的方法之一。您有一个在初始化之前init调用该函数的块。当一个类被实例化时,所有的属性初始化和块都按从上到下的顺序被调用。initializeTonight()tonightinit

您可能认为它是安全的,因为 的值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很容易出错。


推荐阅读