简体   繁体   English

StateFlow 收集发出 NullPointerException

[英]StateFlow collect emit NullPointerException

My repository layer have a MutableStateFlow , collecting it in my ViewModel.我的存储库层有一个MutableStateFlow ,将它收集在我的 ViewModel 中。 I am getting this NPE on some user devices我在一些用户设备上得到这个 NPE

Fatal Exception: java.lang.NullPointerException
       at a.b.c.ui.viewmodel.HomeViewModel$collectFlowState$$inlined$collect$1.emit(HomeViewModel.java:189)
       at a.b.c.ui.viewmodel.HomeViewModel$collectFlowState$$inlined$collect$1$1.invokeSuspend(HomeViewModel.java:12)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(BaseContinuationImpl.java:33)
       at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTaskKt.java:176)
       at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTaskKt.java:111)
       at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.java:308)
       at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.java:318)
       at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.java:400)
       at kotlinx.coroutines.android.HandlerContext$scheduleResumeAfterDelay$$inlined$Runnable$1.run(HandlerContext.java:19)
       at android.os.Handler.handleCallback(Handler.java:883)
       at android.os.Handler.dispatchMessage(Handler.java:100)
       at android.os.Looper.loop(Looper.java:237)
       at android.app.ActivityThread.main(ActivityThread.java:7830)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1040)

MutableStateFlow is of non-null data, if the data is somehow null the app would have crashed earlier. MutableStateFlow是非空数据,如果数据以某种方式是 null,则应用程序会更早崩溃。

An example how I am using StateFlow on the repository (producer) layer:我如何在存储库(生产者)层上使用StateFlow的示例:

data class ApiData(...)
private val INITIAL = ApiData(...)
private var someState = INITIAL

private val dataSF = MutableStateFlow(someState)

fun dataFlow() = dataSF

// called on remote api success, we poll for updated data (delta) from the server
fun onDataChangeAvailable(x: Int, y: Double) {
        someState = someState.copy(x = x, y= y)
        dataSF.value = someState
}

The ViewModel (consumer) side: ViewModel(消费者)方面:

private val repository // constructor injected; repository is Application scoped
private val job = SupervisorJob()
private val uiScope = CoroutineScope(Dispatchers.Main + job)
// Viewmodel init block
init {
     uiScope.launch {
                repository.dataFlow().collect { // crash sometimes here.
                    // consume values
                }
        }
}

override fun onCleared() {
        job.cancel()
        super.onCleared()
    }

From the StateFlow doc来自StateFlow 文档

State flow never completes. State 流程永远不会完成。 A call to Flow.collect on a state flow never completes normally, and neither does a coroutine started by the Flow.launchIn function.在 state 流上对 Flow.collect 的调用永远不会正常完成,由 Flow.launchIn function 启动的协程也不会正常完成。

and the Flow doc recommends to catch exceptions like this并且Flow 文档建议捕获这样的异常

try {
    flow.collect { value ->
        println("Received $value")
    }
} catch (e: Exception) {
    println("The flow has thrown an exception: $e")
}

So is it recommended to swallow all exceptions from the collect of a StateFlow or only those thrown by the producer end?那么是建议吞下collect StateFlow中的所有异常,还是只吞下生产者端抛出的异常? What is the cause of the NPE in general? NPE的一般原因是什么?

"If we just call cancel, it doesn't mean that the coroutine work will just stop." “如果我们只是调用取消,并不意味着协程工作就会停止。”

I resolve this problem checking if the coroutine is active using ensureActive()我使用ensureActive()检查协程是否处于活动状态,解决了这个问题

...
       job = uiScope.launch {
                ensureActive()
                repository.dataFlow().collect { // crash sometimes here.
                    // consume values
                }
        }

override fun onCleared() {
        job.cancel()
        super.onCleared()
    }

You can see detail of this solutin in How to cancel collect coroutine StateFlow?您可以在How to cancel collect coroutine StateFlow? 中查看此解决方案的详细信息?

I faced the same problem.我遇到了同样的问题。 Actually I was mapping the StateFlow to something else and collecting that.实际上,我正在将 StateFlow 映射到其他东西并收集它。 In the mapping process I was using !!在我使用的映射过程中!! notation.符号。 Removing that notation solved my problem.删除该符号解决了我的问题。

I think this could solve your problem我认为这可以解决您的问题

private val dataSF = MutableStateFlow<Int?>(someState)

I faced the same problem but it was happening when recreates the fragment because Stateflow still has an old value so simply add a check before call flow.collect , check for flow contains init value something like this我遇到了同样的问题,但是在重新创建片段时发生了这种情况,因为Stateflow仍然有一个旧值,所以只需在调用flow.collect之前添加一个检查,检查流是否包含类似这样的初始化值

if (viewModel.mutableStateFlow.value == YourInitValue) {
        lifecycleScope.launch {
            viewModel.mutableStateFlow.timeLineData.collect {
        
            }
        }
    }

because if stateflow didn't contain init value that means collect method called before因为如果 stateflow 不包含 init 值,则表示之前调用了 collect 方法

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM