Android:使用 viewModelscope 和 withContext 时不可能出现 NullPointerException
Posted
技术标签:
【中文标题】Android:使用 viewModelscope 和 withContext 时不可能出现 NullPointerException【英文标题】:Android: Impossible NullPointerException when using viewModelscope and withContext 【发布时间】: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.<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()
,并将返回类型从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)
【问题讨论】:
最简单的解决方案是停止编写可能产生空指针的代码。emailEntity.value?.let
或者做一些空值检查,如果它是 null 则分配一个默认值,等等
@a_local_nobody 好吧,但这不是重点。我的问题是, emailEntity 在任何情况下都不能为空。为什么同一件事在一行为空,而在另一行不为空?而且当我使用您的代码时,该值始终是默认值,而不是预期值!
我了解您想了解它发生的原因,这就是为什么我将其写为评论而不是答案:) 我只是为您提供了一个明显的解决方案,所以您不要不必为此浪费时间
@a_local_nobody 我很欣赏这一点,但我不得不在这上面浪费时间,因为这必须高效,因此应该正确设置值:)
嘿,尝试为您的Mutable Live Data
分配一些值,然后使用MutableLiveData(emailEntity.value?.basePrice!!)
。您可以点击此链接获取分配值的参考=***.com/questions/51305150/…
【参考方案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()
。还是有更简单的选择?以上是关于Android:使用 viewModelscope 和 withContext 时不可能出现 NullPointerException的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin Coroutines viewModelScope 中的改造调用
Kotlin之协程coroutine lifecycleScope 和 viewModelScope源码