android runtime_error ai_generated true

java.lang.StackOverflowError: stack size 8MB at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd

ID: android/compose-recomposition-infinite-loop

Also available as: JSON · Markdown · 中文
86%Fix Rate
83%Confidence
1Evidence
2023-11-20First Seen

Version Compatibility

VersionStatusIntroducedDeprecatedNotes
Compose BOM 2024.01.00 active
Compose Compiler 1.5.8 active
Kotlin 1.9.22 active
Android 14 (API 34) active

Root Cause

Infinite recomposition loop in Jetpack Compose due to a state variable being updated during composition (e.g., calling setState inside a LaunchedEffect without proper key or in a composable lambda that triggers re-render).

generic

中文

Jetpack Compose中无限重组循环,由于状态变量在组合期间被更新(例如,在没有正确键的LaunchedEffect中调用setState,或触发重新渲染的可组合lambda中)。

Official Documentation

https://developer.android.com/jetpack/compose/state#side-effects-in-compose

Workarounds

  1. 90% success Move the state update outside of composition by using LaunchedEffect with a stable key. Example: LaunchedEffect(Unit) { delay(100); myState.value = newValue } instead of LaunchedEffect(myState.value) { ... }
    Move the state update outside of composition by using LaunchedEffect with a stable key. Example: LaunchedEffect(Unit) { delay(100); myState.value = newValue } instead of LaunchedEffect(myState.value) { ... }
  2. 85% success Use remember with a derivedStateOf to compute values without triggering recomposition. Example: val derivedValue by remember { derivedStateOf { computeExpensive(myState.value) } }
    Use remember with a derivedStateOf to compute values without triggering recomposition. Example: val derivedValue by remember { derivedStateOf { computeExpensive(myState.value) } }
  3. 88% success Add a snapshotFlow block to observe state changes outside composition. Example: LaunchedEffect(Unit) { snapshotFlow { myState.value }.collect { /* update other state */ } }
    Add a snapshotFlow block to observe state changes outside composition. Example: LaunchedEffect(Unit) { snapshotFlow { myState.value }.collect { /* update other state */ } }

中文步骤

  1. Move the state update outside of composition by using LaunchedEffect with a stable key. Example: LaunchedEffect(Unit) { delay(100); myState.value = newValue } instead of LaunchedEffect(myState.value) { ... }
  2. Use remember with a derivedStateOf to compute values without triggering recomposition. Example: val derivedValue by remember { derivedStateOf { computeExpensive(myState.value) } }
  3. Add a snapshotFlow block to observe state changes outside composition. Example: LaunchedEffect(Unit) { snapshotFlow { myState.value }.collect { /* update other state */ } }

Dead Ends

Common approaches that don't work:

  1. Increase stack size via AndroidManifest android:largeHeap="true" 95% fail

    largeHeap increases heap memory, not stack size. Stack size is fixed per thread in Android (8MB default). This does not fix the infinite loop.

  2. Wrap the state update in withFrameMillis or withFrameNanos 80% fail

    These APIs schedule work on the next frame but do not prevent the recomposition loop if the state is still updated during composition.