1

我的存储库层有一个MutableStateFlow,在我的 ViewModel 中收集它。我在一些用户设备上得到这个 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是非空数据,如果数据以某种方式为空,则应用程序会更早崩溃。

我如何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
}

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()
    }

来自StateFlow 文档

状态流永远不会完成。对状态流的 Flow.collect 调用永远不会正常完成,由 Flow.launchIn 函数启动的协程也不会正常完成。

并且Flow 文档建议捕获这样的异常

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

那么是否建议吞下collecta 中的所有异常StateFlow或仅吞下生产者端抛出的异常?NPE的一般原因是什么?

4

4 回答 4

1

“如果我们只是调用取消,并不意味着协程工作就会停止。”

我解决了这个问题,检查协程是否处于活动状态ensureActive()

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

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

您可以在 How to cancel collect coroutine StateFlow? 中查看此解决方案的详细信息?

于 2021-08-09T08:51:11.027 回答
1

我遇到了同样的问题。实际上,我正在将 StateFlow 映射到其他东西并收集它。在映射过程中我使用了!!符号!删除该符号解决了我的问题。

于 2022-01-17T14:31:46.027 回答
0

我认为这可以解决您的问题

private val dataSF = MutableStateFlow<Int?>(someState)
于 2020-12-31T13:42:09.293 回答
0

我遇到了同样的问题,但是在重新创建片段时发生了这种情况,因为Stateflow仍然有一个旧值,所以只需在调用之前添加一个检查flow.collect,检查流是否包含类似这样的初始化值

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

因为如果 stateflow 不包含 init 值,则表示之前调用了 collect 方法

于 2021-02-12T22:59:16.460 回答