Status Update
Comments
ch...@google.com <ch...@google.com>
ch...@google.com <ch...@google.com> #2
Triage notes: Still P3, still a real issue. Compat issue.
ds...@gmail.com <ds...@gmail.com> #3
Hello the Home Assistant Wear OS app is also facing this same exact issue it seems. The traceback in our issue is very close to the issue posted here.
According to play console we have about 1k impacted users.
Exception java.util.ConcurrentModificationException:
at androidx.compose.runtime.snapshots.StateListIterator.validateModification (SnapshotStateList.kt:295)
at androidx.compose.runtime.snapshots.StateListIterator.next (SnapshotStateList.kt:274)
at io.homeassistant.companion.android.home.views.MainViewKt$MainView$1$3$1.invoke (MainView.kt:382)
at io.homeassistant.companion.android.home.views.MainViewKt$MainView$1$3$1.invoke (MainView.kt:77)
at androidx.wear.compose.material.ScalingLazyColumnKt$ScalingLazyColumn$1$1$2$1.invoke (ScalingLazyColumn.kt:425)
at androidx.wear.compose.material.ScalingLazyColumnKt$ScalingLazyColumn$1$1$2$1.invoke (ScalingLazyColumn.kt:410)
at androidx.compose.foundation.lazy.LazyListItemProviderKt$rememberLazyListItemProvider$1$itemProviderState$1.invoke (LazyListItemProvider.kt:54)
at androidx.compose.foundation.lazy.LazyListItemProviderKt$rememberLazyListItemProvider$1$itemProviderState$1.invoke (LazyListItemProvider.kt:53)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe (Snapshot.kt:2200)
at androidx.compose.runtime.DerivedSnapshotState.currentRecord (DerivedState.kt:161)
at androidx.compose.runtime.DerivedSnapshotState.getCurrentValue (DerivedState.kt:231)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.recordInvalidation (SnapshotStateObserver.kt:523)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.drainChanges (SnapshotStateObserver.kt:66)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.access$drainChanges (SnapshotStateObserver.kt:38)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$applyObserver$1.invoke (SnapshotStateObserver.kt:45)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$applyObserver$1.invoke (SnapshotStateObserver.kt:43)
at androidx.compose.runtime.snapshots.MutableSnapshot.apply (Snapshot.kt:748)
at androidx.compose.runtime.Recomposer.applyAndCheck (Recomposer.kt:1124)
at androidx.compose.runtime.Recomposer.performRecompose (Recomposer.kt:1483)
at androidx.compose.runtime.Recomposer.access$performRecompose (Recomposer.kt:124)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke (Recomposer.kt:541)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke (Recomposer.kt:510)
at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame (AndroidUiFrameClock.android.kt:34)
at androidx.compose.ui.platform.AndroidUiDispatcher.performFrameDispatch (AndroidUiDispatcher.android.kt:109)
at androidx.compose.ui.platform.AndroidUiDispatcher.access$performFrameDispatch (AndroidUiDispatcher.android.kt:41)
at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame (AndroidUiDispatcher.android.kt:69)
at android.view.Choreographer$CallbackRecord.run (Choreographer.java:996)
at android.view.Choreographer.doCallbacks (Choreographer.java:796)
at android.view.Choreographer.doFrame (Choreographer.java:727)
at android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:983)
at android.os.Handler.handleCallback (Handler.java:938)
at android.os.Handler.dispatchMessage (Handler.java:99)
at android.os.Looper.loop (Looper.java:246)
at android.app.ActivityThread.main (ActivityThread.java:7690)
at java.lang.reflect.Method.invoke
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:593)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:995)
ch...@google.com <ch...@google.com> #4
Part (2) of this was implemented in aosp/2751617 and should be available in 1.6 which will remove the exception.
However, I still recommend against any derivedStateOf
lambda from having side-effects as described above as it is not possible to produce a consistent snapshot if derivedStateOf
has a side-effect as the result of the .value
call is not guaranteed to be consistent with the snapshot it is produced for.
al...@google.com <al...@google.com> #5
Triage notes, there is still some remaining work here.
he...@lemi.bot <he...@lemi.bot> #6
val sessions by viewModel.sessions.collectAsStateWithLifecycle()
val selectedSessions = remember { mutableStateListOf<Session>() }
LaunchedEffect(sessions) {
selectedSessions.removeIf { it !in sessions.orEmpty() }
}
I'm having a similar issue
java.util.ConcurrentModificationException
at androidx.compose.runtime.snapshots.StateListIterator.validateModification(SnapshotStateList.kt:320)
at androidx.compose.runtime.snapshots.StateListIterator.next(SnapshotStateList.kt:296)
What's the correct way to implement this?
ch...@google.com <ch...@google.com> #7
Part (2) of this change, relaxing under what conditions a concurrent modification exception is generated, landed in 1.6.
For example, the following code does not generate a concurrent modification exception beginning with 1.6.
Column {
val list = remember {
mutableStateListOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
}
for (item in list) {
Text("Item $item")
}
LaunchedEffect(list) {
delay(500)
list.removeIf { item -> item % 2 != 0}
}
}
al...@google.com <al...@google.com> #8
Triage notes: Still some P3
work pending here before we can close this out.
Description
SnapshotStateList.iterator()
returns an iterator that throws when the snapshot is modified while iterating values. I expected this to behave similarly toMutableState
, where reads operate on a snapshot record and concurrent writes update the snapshot state.To do this with
SnapshotStateList
, one must remember to calltoList()
to obtain thePersistentList
held in the current snapshot record. Unfortunately, this is difficult to remember to do: a simple read or indexed access can operate concurrently, but many Kotlin stdlib extensions invokeiterator()
under the hood to iterate the list, includingmap
,fold
,forEach
, etc.Ideally, one should be able to read whlie iterating on a snapshot value, but fail with the CME when modifying through the iterator.
A similar problem exists with the iterator returned by
SnapshotStateMap.entries.iterator()
, and many Kotlin stdlib extensions call.entries.iterator()
internally.Jetpack Compose version: 1.4.0-beta02 Jetpack Compose component used: Runtime Android Studio Build: 2022.2.1 Beta 4 Kotlin version: 1.8.10
Steps to Reproduce or Code Sample to Reproduce:
Stack trace (if applicable):