Android:使用 viewModelscope 和 withContext 时不可能出现 NullPointerException

Android: Impossible NullPointerException when using viewModelscope and withContext

我的问题是,我得到了一个不可能的 NullPointerException。当我在不使用 elvis-operator 的情况下从 price variable 访问我的 emailEntity 数据时,我的 price variable 变为空并且我得到 NullPointerException.

现在问题来了:当我在我的 price variable 处使用 elvis-operator 并在一个函数中访问我的 emailEntity 数据时,我 没有得到 a NullPointerException 并且价格设置正确。我做错了什么?

基本代码

class EmailViewModel @ViewModelInject constructor() : ViewModel() {

      // This is the value I access from my price variable and the function
      private val emailEntity = MutableLiveData<EmailEntity?>()

      // Setting the value of my emailEntity here
      init {
          // I have to use viewModelScope because "getCalibratePrice and getRepairPrice" are suspend functions
          viewModelScope.launch {
              withContext(Dispatchers.IO) {
                    when(subject.value.toString()) {
                       "Toast" -> emailEntity.postValue(emailRepository.getCalibratePrice())
                       else -> emailEntity.postValue(emailRepository.getRepairPrice())
                    }
              }
          }
      }
}

问题代码

   // NullPointerException
   val price = MutableLiveData(emailEntity.value?.basePrice!!)

  fun checkIfPriceIsInitialized() {
    Timber.d("Emailprice is ${emailEntity.value.basePrice}")
  }

工作代码

   // NO NullPointerException but value is now always 0F
   val price = MutableLiveData(emailEntity.value?.basePrice ?: 0F)

  // EmailEntity price is correctly set here!!!
  fun checkIfPriceIsInitialized() {
    Timber.d("Emailprice is ${emailEntity.value.basePrice}")
  }

堆栈跟踪

java.lang.NullPointerException
    at com.example.app.framework.ui.viewmodel.EmailViewModel.<init>(EmailViewModel.kt:164)
    at com.example.app.framework.ui.viewmodel.EmailViewModel_AssistedFactory.create(EmailViewModel_AssistedFactory.java:58)
    at com.example.app.framework.ui.viewmodel.EmailViewModel_AssistedFactory.create(EmailViewModel_AssistedFactory.java:20)
    at androidx.hilt.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:76)
    at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:69)
    at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:185)
    at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
    at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:54)
    at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:41)
    at com.example.app.framework.ui.view.fragments.home.calibrateAndRepair.CalibrateRepairMessageFragment.getViewModel(Unknown Source:2)
    at com.example.app.framework.ui.view.fragments.home.calibrateAndRepair.CalibrateRepairMessageFragment.getViewModel(CalibrateRepairMessageFragment.kt:26)
    at com.example.app.framework.ui.view.basefragments.BaseFragment.onCreateView(BaseFragment.kt:30)
    at com.example.app.framework.ui.view.basefragments.EmailFragment.onCreateView(EmailFragment.kt:54)
    at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2699)
    at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:320)
    at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1199)
    at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2236)
    at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2009)
    at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1965)
    at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1861)
    at androidx.fragment.app.FragmentManager.run(FragmentManager.java:413)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:7356)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

EmailViewModel.<init>(EmailViewModel.kt:164) 指向 -> val price = MutableLiveData(emailEntity.value?.basePrice!!)

请记住,我是从头开始使用 kotlin 协程的。因此我 100% 不知道它是如何工作的

编辑

这是我的存储库:

interface EmailRepository {
    fun sendEmail(email: Email): Flow<EmailStatus<Unit>>
    suspend fun getCalibratePrice(): Flow<EmailEntity?>
    suspend fun getRepairPrice(): Flow<EmailEntity?>
}

这是我的实现:

class EmailRepositoryImpl @Inject constructor(
    private val db: FirebaseFirestore
) : EmailRepository {

override suspend fun getCalibratePrice(): Flow<EmailEntity?> = flow {
        val result = db.collection("emailprice").document("Kalibrieren").get().await()
        val emailEntity = result.toObject<EmailEntity?>()
        emit(emailEntity)
    }.catch {
        Timber.d("Error on getCalibrate Price")
    }.flowOn(Dispatchers.Main)

    override suspend fun getRepairPrice(): Flow<EmailEntity?> = flow {
        val collection = db.collection("emailprice").document("Reparieren").get().await()
        val emailEntity = collection.toObject<EmailEntity?>()
        emit(emailEntity)
    }.catch {
        Timber.d("Error on getRepairPrice")
    }.flowOn(Dispatchers.Main)

}

另一种方法是在最后使用 .single() 并将 return 类型从 Flow<EmailEntity?> 更改为 EmailEntity

编辑 2

private var emailEntity: EmailEntity = EmailEntity("", 50F)

init {
    viewModelScope.launch {
        when(subject.value.toString()) {
            context.getString(R.string.home_calibrate_card_headline) -> emailRepository.getCalibratePrice().collect {
                emailEntity = it ?: EmailEntity("Error", 100F)
            }
            else -> emailRepository.getRepairPrice().collect {
                emailEntity = it ?: EmailEntity("Error Zwei", 150F)
            }
        }
    }
}

// Price is 50 and does not change..
val price = MutableLiveData(emailEntity.basePrice)

您的协程是 运行 异步代码。当你的 EmailViewModel 被实例化时,协同程序还没有完成 运行,所以在那个时候,LiveData 的值仍然是空的。在协程完成 运行.

之前,您必须尝试立即从主线程检索值

通常使用 LiveData,您几乎从不直接检索值。相反,您应该使用回调观察 LiveData,以便您可以在它获得值 时做出反应 ,这不会立即发生在挂起函数和协程中。

顺便说一下,您应该只从主线程更新 LiveData,而您没有使用协程执行此操作。假设您的挂起函数正确地委托给后台线程,如果它们来自您使用 Room 库的情况就是这种情况,您应该从协程中删除包装 withContext 块。始终可以从 Main Dispatcher 安全地调用一个正确组合的挂起函数,并将在内部根据需要委托给后台调度程序。