Reputation: 2560
My repository layer have a MutableStateFlow
, collecting it in my ViewModel. I am getting this NPE on some user devices
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.
An example how I am using StateFlow
on the repository (producer) layer:
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:
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
State flow never completes. A call to Flow.collect on a state flow never completes normally, and neither does a coroutine started by the Flow.launchIn function.
and the Flow doc recommends to catch exceptions like this
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? What is the cause of the NPE in general?
Upvotes: 1
Views: 3634
Reputation: 11
I faced the same problem. Actually I was mapping the StateFlow to something else and collecting that.
In the mapping process I was using !!
notation!
Removing that notation solved my problem.
Upvotes: 1
Reputation: 11
"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()
...
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?
Upvotes: 1
Reputation: 3625
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
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
Upvotes: 1
Reputation: 718
I think this could solve your problem
private val dataSF = MutableStateFlow<Int?>(someState)
Upvotes: 0