【问题标题】:Android: Impossible NullPointerException when using viewModelscope and withContextAndroid:使用 viewModelscope 和 withContext 时不可能出现 NullPointerException
【发布时间】:2021-01-07 19:09:38
【问题描述】:

我的问题是,我得到了一个不可能的NullPointerException。当我从我的price variable 访问我的emailEntity 数据而不使用elvis-operator 时,我的price variable 为空,我得到一个NullPointerException

现在问题来了:当我在我的 price variable 上使用 elvis-operator 并在函数中访问我的 emailEntity 数据时,我没有得到 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$4.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.&lt;init&gt;(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(),并将返回类型从Flow&lt;EmailEntity?&gt; 更改为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)

【问题讨论】:

  • 最简单的解决方案是停止编写可能产生空指针的代码。 emailEntity.value?.let{ } 或者做一些空值检查,如果它是 null 则分配一个默认值,等等
  • @a_local_nobody 好吧,但这不是重点。我的问题是, emailEntity 在任何情况下都不能为空。为什么同一件事在一行为空,而在另一行不为空?而且当我使用您的代码时,该值始终是默认值,而不是预期值!
  • 我了解您想了解它发生的原因,这就是为什么我将其写为评论而不是答案:) 我只是为您提供了一个明显的解决方案,所以您不要不必为此浪费时间
  • @a_local_nobody 我很欣赏这一点,但我不得不在这上面浪费时间,因为这必须高效,因此应该正确设置值:)
  • 嘿,尝试为您的Mutable Live Data 分配一些值,然后使用MutableLiveData(emailEntity.value?.basePrice!!)。您可以点击此链接获取分配值的参考=stackoverflow.com/questions/51305150/…

标签: android kotlin nullpointerexception kotlin-coroutines android-mvvm


【解决方案1】:

您的协程正在运行异步代码。当您的EmailViewModel 被实例化时,协程还没有完成运行,所以此时LiveData 的值仍然为空。在协程完成运行之前,您必须尝试立即从主线程中检索该值。

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

顺便说一句,您应该只从主线程更新 LiveData,而您的协程无法做到这一点。假设您的挂起函数正确委托给后台线程,如果它们来自您使用 Room 库,您应该从协程中删除包装 withContext 块。始终可以从 Main Dispatcher 安全地调用正确组合的挂起函数,并在内部根据需要委托给后台调度程序。

【讨论】:

  • 但是我怎样才能从我的视图模型中观察我的实时数据呢?在我的视图模型之外不需要我的 emailEntity,因此无法观察到。我想要实现的是从我的视图模型中“观察”我的 emailRepository 并设置我的价格值。我还没有找到如何做到这一点,当我在我的问题中提供我的 emailRepository 时,您能否将其添加到您的答案中?
  • 哦,在这种情况下,您甚至不需要 LiveData。只需将用于处理检索到的值的代码放在最后的协程中即可。
  • 是的 uuuh,我不知道你想告诉我什么以及如何做 :(
  • 您使用flow 发出单个值是否有原因?
  • 目前,我使用的是flow,但稍后我将使用callbackFlow 更改它,以便从我的firebase cloud firestore 发出emailEntities 流,然后始终获取更新。使用flow 的另一个原因是我不必使用侦听器来获取我的文档。如果不使用流量,我将无法使用DocumentReference.get().await()。还是有更简单的选择?
猜你喜欢
  • 2016-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-11
  • 2015-12-29
  • 1970-01-01
  • 1970-01-01
  • 2012-06-23
相关资源
最近更新 更多