We are sometime seeing tests that fails with:
java.lang.IllegalStateException: The current thread must have a looper!
This seemed to be caused by Composition not happening on the main thread. This is the stacktrace:
java.lang.IllegalStateException: The current thread must have a looper!
at android.view.Choreographer$1.initialValue(Choreographer.java:111)
at android.view.Choreographer$1.initialValue(Choreographer.java:106)
at java.lang.ThreadLocal.setInitialValue(ThreadLocal.java:180)
at java.lang.ThreadLocal.get(ThreadLocal.java:170)
at android.view.Choreographer.getInstance(Choreographer.java:296)
at androidx.compose.foundation.lazy.layout.AndroidPrefetchScheduler.<init>(PrefetchScheduler.android.kt:108)
at androidx.compose.foundation.lazy.layout.PrefetchScheduler_androidKt.rememberDefaultPrefetchScheduler(PrefetchScheduler.android.kt:38)
at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$3.invoke(LazyLayout.kt:90)
at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$3.invoke(LazyLayout.kt:82)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:118)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.foundation.lazy.layout.LazySaveableStateHolderKt$LazySaveableStateHolderProvider$1.invoke(LazySaveableStateHolder.kt:51)
at androidx.compose.foundation.lazy.layout.LazySaveableStateHolderKt$LazySaveableStateHolderProvider$1.invoke(LazySaveableStateHolder.kt:49)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:109)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:401)
at androidx.compose.foundation.lazy.layout.LazySaveableStateHolderKt.LazySaveableStateHolderProvider(LazySaveableStateHolder.kt:49)
at androidx.compose.foundation.lazy.layout.LazyLayoutKt.LazyLayout(LazyLayout.kt:82)
at androidx.compose.foundation.lazy.LazyListKt.LazyList(LazyList.kt:106)
at androidx.compose.foundation.lazy.LazyDslKt.LazyColumn(LazyDsl.kt:368)
at com.example.LazyColumn(LazyColumn.kt:205)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:118)
at androidx.compose.runtime.internal.ComposableLambdaImpl$invoke$1.invoke(ComposableLambda.jvm.kt:130)
at androidx.compose.runtime.internal.ComposableLambdaImpl$invoke$1.invoke(ComposableLambda.jvm.kt:129)
at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:192)
at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2825)
at androidx.compose.runtime.ComposerImpl.skipCurrentGroup(Composer.kt:3116)
at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3607)
at androidx.compose.runtime.ComposerImpl.recompose$runtime_release(Composer.kt:3552)
at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:948)
at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:1206)
at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:132)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke(Recomposer.kt:616)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke(Recomposer.kt:585)
at androidx.compose.ui.test.TestMonotonicFrameClock$withFrameNanos$2$1$1.invoke(TestMonotonicFrameClock.jvm.kt:104)
at androidx.compose.ui.test.TestMonotonicFrameClock$withFrameNanos$2$1$1.invoke(TestMonotonicFrameClock.jvm.kt:103)
at androidx.compose.ui.test.TestMonotonicFrameClock$performFrame$1.invoke(TestMonotonicFrameClock.jvm.kt:149)
at androidx.compose.ui.test.TestMonotonicFrameClock$performFrame$1.invoke(TestMonotonicFrameClock.jvm.kt:132)
at androidx.compose.ui.test.FrameDeferringContinuationInterceptor.runWithoutResumingCoroutines(FrameDeferringContinuationInterceptor.jvm.kt:60)
at androidx.compose.ui.test.TestMonotonicFrameClock.performFrame(TestMonotonicFrameClock.jvm.kt:132)
at androidx.compose.ui.test.TestMonotonicFrameClock.access$performFrame(TestMonotonicFrameClock.jvm.kt:53)
at androidx.compose.ui.test.TestMonotonicFrameClock$withFrameNanos$2$1$2.invokeSuspend(TestMonotonicFrameClock.jvm.kt:110)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:359)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:358)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:124)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:52)
at kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:43)
at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
at androidx.compose.ui.test.TestMonotonicFrameClock.withFrameNanos(TestMonotonicFrameClock.jvm.kt:108)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend(Recomposer.kt:585)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at androidx.compose.ui.test.ApplyingContinuationInterceptor$SendApplyContinuation.resumeWith(ApplyingContinuationInterceptor.kt:65)
at androidx.compose.ui.test.FrameDeferringContinuationInterceptor$FrameDeferredContinuation.resumeWith(FrameDeferringContinuationInterceptor.jvm.kt:194)
at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:165)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:154)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:470)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$kotlinx_coroutines_core(CancellableContinuationImpl.kt:504)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$kotlinx_coroutines_core$default(CancellableContinuationImpl.kt:493)
at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:359)
at androidx.compose.runtime.Recomposer$recompositionRunner$2$unregisterApplyObserver$1.invoke(Recomposer.kt:1042)
at androidx.compose.runtime.Recomposer$recompositionRunner$2$unregisterApplyObserver$1.invoke(Recomposer.kt:1026)
at androidx.compose.runtime.snapshots.SnapshotKt.advanceGlobalSnapshot(Snapshot.kt:1945)
at androidx.compose.runtime.snapshots.SnapshotKt.advanceGlobalSnapshot(Snapshot.kt:1960)
at androidx.compose.runtime.snapshots.SnapshotKt.access$advanceGlobalSnapshot(Snapshot.kt:1)
at androidx.compose.runtime.snapshots.Snapshot$Companion.sendApplyNotifications(Snapshot.kt:692)
at androidx.compose.ui.test.ApplyingContinuationInterceptor$SendApplyContinuation.resumeWith(ApplyingContinuationInterceptor.kt:66)
at androidx.compose.ui.test.FrameDeferringContinuationInterceptor$FrameDeferredContinuation.resumeWith(FrameDeferringContinuationInterceptor.jvm.kt:194)
at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:165)
at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:154)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:470)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$kotlinx_coroutines_core(CancellableContinuationImpl.kt:504)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$kotlinx_coroutines_core$default(CancellableContinuationImpl.kt:493)
at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:359)
at kotlinx.coroutines.flow.SharedFlowImpl.tryEmit(SharedFlow.kt:414)
at kotlinx.coroutines.flow.SharedFlowImpl.emit$suspendImpl(SharedFlow.kt:419)
at kotlinx.coroutines.flow.SharedFlowImpl.emit(Unknown Source:0)
at com.airbnb.mvrx.CoroutinesStateStore$flushQueuesOnce$2$1.invokeSuspend(CoroutinesStateStore.kt:87)
at com.airbnb.mvrx.CoroutinesStateStore$flushQueuesOnce$2$1.invoke(Unknown Source:8)
at com.airbnb.mvrx.CoroutinesStateStore$flushQueuesOnce$2$1.invoke(Unknown Source:4)
at kotlinx.coroutines.selects.SelectImplementation$ClauseData.invokeBlock(Select.kt:846)
at kotlinx.coroutines.selects.SelectImplementation.complete(Select.kt:715)
at kotlinx.coroutines.selects.SelectImplementation.doSelectSuspend(Select.kt:456)
at kotlinx.coroutines.selects.SelectImplementation.access$doSelectSuspend(Select.kt:251)
at kotlinx.coroutines.selects.SelectImplementation$doSelectSuspend$1.invokeSuspend(Unknown Source:14)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:101)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:920)
I could not determine why sometime the CoroutinesStateStore emits in a background coroutine. Could this be caused by the Channel hitting a buffer limit, and then resuming?
A search in the Android Issue Tracker didn't result in any bug that could be present in Compose
In any case, I was able to avoid this exception by specifying the context in collectAsState:
@Composable
fun <VM : MavericksViewModel<S>, S : MavericksState> VM.collectAsState(): State<S> {
return stateFlow.collectAsState(initial = withState(this) { it }, context = Dispatchers.Main)
}
As such I suggest that we modify collectAsState() to allow the caller to be able to specify a coroutine context. I think this context should be Dispatchers.Main by default.
We are sometime seeing tests that fails with:
This seemed to be caused by Composition not happening on the
mainthread. This is the stacktrace:I could not determine why sometime the
CoroutinesStateStoreemits in a background coroutine. Could this be caused by theChannelhitting a buffer limit, and then resuming?A search in the Android Issue Tracker didn't result in any bug that could be present in Compose
In any case, I was able to avoid this exception by specifying the context in
collectAsState:As such I suggest that we modify
collectAsState()to allow the caller to be able to specify a coroutine context. I think this context should beDispatchers.Mainby default.