Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Coroutine Launched Outside Side Effect in rememberAsyncImagePainterresults in Unsupported concurrent change during composition crash #1764

Open
mmoczkowski opened this issue May 15, 2023 · 3 comments
Labels
help wanted Issues that are up for grabs + are good candidates for community PRs

Comments

@mmoczkowski
Copy link

Cause: Coroutine Launched Outside Side Effect in rememberAsyncImagePainter

Description:
I have identified a bug in the Coil Compose library that appears to originate from the rememberAsyncImagePainter composable function. The bug arises when a onRemembered function is invoked inside the rememberAsyncImagePainter function.

onRemembered is a non-composable function that establishes a new coroutine scope and launches a coroutine. The issue appears to be that the coroutine is being launched outside of a side effect block within a composable function, which is not a good practice in the Jetpack Compose framework.

The main reason why launching coroutines outside a side effect in a composable function is problematic is due to the life-cycle of the composable function. Jetpack Compose recomposes (reruns) composable functions for various reasons, such as when the data they're displaying changes. This means that a coroutine could be launched multiple times if it's not managed within a side effect block such as LaunchedEffect, DisposableEffect, or produceState.

Launching a coroutine outside a side effect block can also result in memory leaks as it's not tied to the lifecycle of the composable function. Side effects ensure that when the composable leaves the composition, any resources (like the coroutine) are cleaned up.

This could lead to unexpected behavior, as the scope of the coroutine isn't necessarily tied to the lifecycle of the composable function, which can result in unexpected outcomes such as memory leaks or even application crashes.

To Reproduce
The minimum reproducible sample can be found here: https://github.com/android/nowinandroid/tree/reproduce_crash
Launch the RotateTest instrumentation test. It will rotate the device back and forth eventually causing the crash. It's not deterministic and might take a few iterations.

Logs/Screenshots

java.lang.IllegalStateException: Unsupported concurrent change during composition. A state object was modified by composition as well as being modified outside composition.
at androidx.compose.runtime.Recomposer.applyAndCheck(Recomposer.kt:1127)
at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:1460)
at androidx.compose.runtime.ComposerImpl$CompositionContextImpl.composeInitial$runtime_release(Composer.kt:3973)
at androidx.compose.runtime.ComposerImpl$CompositionContextImpl.composeInitial$runtime_release(Composer.kt:3973)
at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:519)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcomposeInto(SubcomposeLayout.kt:466)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:439)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:430)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:419)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$Scope.subcompose(SubcomposeLayout.kt:740)
at androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScopeImpl.measure-0kLqBqw(LazyLayoutMeasureScope.kt:118)
at androidx.compose.foundation.lazy.LazyMeasuredItemProvider.getAndMeasure-ZjPyQlc(LazyMeasuredItemProvider.kt:47)
at androidx.compose.foundation.lazy.LazyListMeasureKt.measureLazyList-Hh3qtAg(LazyListMeasure.kt:164)
at androidx.compose.foundation.lazy.LazyListKt$rememberLazyListMeasurePolicy$1$1.invoke-0kLqBqw(LazyList.kt:299)
at androidx.compose.foundation.lazy.LazyListKt$rememberLazyListMeasurePolicy$1$1.invoke(LazyList.kt:190)
at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$1$2$1.invoke-0kLqBqw(LazyLayout.kt:71)
at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$1$2$1.invoke(LazyLayout.kt:69)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1.measure-3p2s80s(SubcomposeLayout.kt:598)
at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:103)
at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$2.invoke-3p2s80s(AndroidOverscroll.kt:578)
at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$2.invoke(AndroidOverscroll.kt:577)
at androidx.compose.ui.layout.LayoutModifierImpl.measure-3p2s80s(LayoutModifier.kt:294)
at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:155)
at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$1.invoke-3p2s80s(AndroidOverscroll.kt:562)
at androidx.compose.foundation.AndroidOverscrollKt$StretchOverscrollNonClippingLayer$1.invoke(AndroidOverscroll.kt:561)
at androidx.compose.ui.layout.LayoutModifierImpl.measure-3p2s80s(LayoutModifier.kt:294)
at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:155)
at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:635)
at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:155)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1090)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1086)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2200)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:234)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:230)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
2023-05-15 16:28:46.180 21093-21116 TestRunner              com...gle.samples.apps.nowinandroid  E  	at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:230)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:120)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:107)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1086)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:36)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:342)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:321)
at androidx.compose.foundation.layout.BoxKt$boxMeasurePolicy$1.measure-3p2s80s(Box.kt:115)
at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:103)
at androidx.compose.ui.graphics.BlockGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:572)
at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:155)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1090)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1086)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2200)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:234)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:230)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:230)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:120)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:107)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1086)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:36)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:342)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:321)
at androidx.compose.foundation.layout.BoxKt$boxMeasurePolicy$1.measure-3p2s80s(Box.kt:115)
at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:103)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1090)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1086)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2200)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:234)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:230)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:230)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:120)
2023-05-15 16:28:46.180 21093-21116 TestRunner              com...gle.samples.apps.nowinandroid  E  	at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:107)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1086)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:36)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:342)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:321)
at androidx.compose.foundation.layout.RowColumnMeasurementHelper.measureWithoutPlacing-_EkL_-Y(RowColumnMeasurementHelper.kt:112)
at androidx.compose.foundation.layout.RowColumnImplKt$rowColumnMeasurePolicy$1.measure-3p2s80s(RowColumnImpl.kt:70)
at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:103)
at androidx.compose.foundation.layout.InsetsPaddingModifier.measure-3p2s80s(WindowInsetsPadding.kt:171)
at androidx.compose.ui.node.BackwardsCompatNode.measure-3p2s80s(BackwardsCompatNode.kt:323)
at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:155)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1090)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1086)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2200)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:234)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:230)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:230)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:120)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:107)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1086)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:36)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:342)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:321)
at androidx.compose.material3.ScaffoldKt$ScaffoldLayout$1$1$1.invoke(Scaffold.kt:240)
at androidx.compose.material3.ScaffoldKt$ScaffoldLayout$1$1$1.invoke(Scaffold.kt:128)
at androidx.compose.ui.layout.MeasureScope$layout$1.placeChildren(MeasureScope.kt:70)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1$measure$1.placeChildren(SubcomposeLayout.kt:610)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate$layoutChildren$1$1.invoke(LayoutNodeLayoutDelegate.kt:276)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate$layoutChildren$1$1.invoke(LayoutNodeLayoutDelegate.kt:268)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2200)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:234)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:230)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
2023-05-15 16:28:46.181 21093-21116 TestRunner              com...gle.samples.apps.nowinandroid  E  	at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:230)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:120)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeLayoutSnapshotReads$ui_release(OwnerSnapshotObserver.kt:77)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.layoutChildren(LayoutNodeLayoutDelegate.kt:268)
at androidx.compose.ui.node.LayoutNode.onNodePlaced$ui_release(LayoutNode.kt:956)
at androidx.compose.ui.node.InnerNodeCoordinator.placeAt-f8xVGno(InnerNodeCoordinator.kt:137)
at androidx.compose.ui.layout.Placeable.access$placeAt-f8xVGno(Placeable.kt:35)
at androidx.compose.ui.layout.Placeable$PlacementScope.place-70tqf50(Placeable.kt:445)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate$placeOuterCoordinator$1.invoke(LayoutNodeLayoutDelegate.kt:451)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate$placeOuterCoordinator$1.invoke(LayoutNodeLayoutDelegate.kt:445)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2200)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:234)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$observeReads$1$1.invoke(SnapshotStateObserver.kt:230)
at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:230)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:120)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeLayoutModifierSnapshotReads$ui_release(OwnerSnapshotObserver.kt:92)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.placeOuterCoordinator-f8xVGno(LayoutNodeLayoutDelegate.kt:445)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.replace(LayoutNodeLayoutDelegate.kt:466)
at androidx.compose.ui.node.LayoutNode.replace$ui_release(LayoutNode.kt:844)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:445)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.access$remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:39)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout(MeasureAndLayoutDelegate.kt:330)
at androidx.compose.ui.platform.AndroidComposeView.measureAndLayout(AndroidComposeView.android.kt:805)
at androidx.compose.ui.node.Owner.measureAndLayout$default(Owner.kt:221)
at androidx.compose.ui.platform.AndroidComposeView.measureAndLayoutForTest(AndroidComposeView.android.kt:860)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$frameClock$1.invoke(ComposeUiTest.android.kt:262)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$frameClock$1.invoke(ComposeUiTest.android.kt:254)
at androidx.compose.ui.test.TestMonotonicFrameClock$performFrame$1.invoke(TestMonotonicFrameClock.jvm.kt:152)
at androidx.compose.ui.test.TestMonotonicFrameClock$performFrame$1.invoke(TestMonotonicFrameClock.jvm.kt:132)
at androidx.compose.ui.test.FrameDeferringContinuationInterceptor.runWithoutResumingCoroutines(FrameDeferringContinuationInterceptor.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.DispatchedTaskKt.resume(DispatchedTask.kt:234)
2023-05-15 16:28:46.181 21093-21116 TestRunner              com...gle.samples.apps.nowinandroid  E  	at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:166)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:431)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:420)
at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.kt:518)
at kotlinx.coroutines.test.CancellableContinuationRunnable.run(TestDispatcher.kt:50)
at kotlinx.coroutines.test.TestDispatcher.processEvent$kotlinx_coroutines_test(TestDispatcher.kt:28)
at kotlinx.coroutines.test.TestCoroutineScheduler.runCurrent(TestCoroutineScheduler.kt:135)
at androidx.compose.ui.test.junit4.AbstractMainTestClock$advanceDispatcher$1.invoke(AbstractMainTestClock.kt:74)
at androidx.compose.ui.test.junit4.AbstractMainTestClock$advanceDispatcher$1.invoke(AbstractMainTestClock.kt:67)
at androidx.compose.ui.test.junit4.AndroidSynchronization_androidKt.runOnUiThread(AndroidSynchronization.android.kt:33)
at androidx.compose.ui.test.junit4.MainTestClockImpl$1.invoke(MainTestClockImpl.android.kt:32)
at androidx.compose.ui.test.junit4.MainTestClockImpl$1.invoke(MainTestClockImpl.android.kt:32)
at androidx.compose.ui.test.junit4.AbstractMainTestClock.advanceDispatcher(AbstractMainTestClock.kt:67)
at androidx.compose.ui.test.junit4.AbstractMainTestClock.advanceTimeByFrame(AbstractMainTestClock.kt:38)
at androidx.compose.ui.test.junit4.ComposeIdlingResource.isIdleNow(ComposeIdlingResource.android.kt:74)
at androidx.compose.ui.test.junit4.IdlingResourceRegistry.areAllResourcesIdle(IdlingResourceRegistry.jvm.kt:124)
at androidx.compose.ui.test.junit4.IdlingResourceRegistry.isIdleOrEnsurePolling$ui_test_junit4_release(IdlingResourceRegistry.jvm.kt:103)
at androidx.compose.ui.test.junit4.EspressoLink.isIdleNow(EspressoLink.android.kt:44)
at androidx.test.espresso.base.IdlingResourceRegistry.allResourcesAreIdle(IdlingResourceRegistry.java:4)
at androidx.test.espresso.base.IdlingResourceRegistry$6.isIdleNow(IdlingResourceRegistry.java:1)
at androidx.test.espresso.base.UiControllerImpl.loopMainThreadUntilIdle(UiControllerImpl.java:10)
at androidx.test.espresso.Espresso$1.run(Espresso.java:1)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:463)
at java.util.concurrent.FutureTask.run(FutureTask.java:264)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7898)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

Version
Coil version 2.3.0
Tested on: Pixel 6 API 32, Pixel 7 API 33, Samsung Fold 3 API 31

@colinrtwhite
Copy link
Member

colinrtwhite commented May 16, 2023

Thanks for the report! I'll take a closer look, however I don't think we can use LaunchedEffect unfortunately as this would delay checking the memory cache synchronously, which would add one frame of delay when displaying a cached image. Do you know if there's a way to launch an effect synchronously in Compose?

@colinrtwhite colinrtwhite added the help wanted Issues that are up for grabs + are good candidates for community PRs label May 16, 2023
@makaronis
Copy link

What about this problem ? I was debugging my app to identify what causing Unsupported concurrent change during composition and come to conclusion that AsyncImage is the problem, it happens when i'm navigating back to some screen with asyncImg in it. Is there any workaround for that? i'm not sure i can use coil when my app goes to prod(

@MarcusOuelletus
Copy link

TLDR; when I call a NavHostController's navigate() function right after the AsyncImage is rendered, I get this error.

I am also experiencing the same issue. I have a list composable which contains a LazyColumn, each row in the column has an AsyncImage and clicking on the row navigates to another screen. At the top of the list composable I call a loadPhotoUrl function which calls an API, when the url is returned the AsyncImage composable is rendered.

If I quickly click on a row, navigate() is called and I get the Unsupported Concurrent Change error, if I wait half a second, I never get the error. If this always happens within a half second of the API call returning this behavior will seem very unpredictable for users.

Noticed the error on 2.3.0, updated to 2.4.0 and am still getting the same behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Issues that are up for grabs + are good candidates for community PRs
Projects
None yet
Development

No branches or pull requests

4 participants