Hey all :slightly_smiling_face: I'm having a Snap...
# compose
q
Hey all 🙂 I'm having a Snapshot
State
read error:
Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied
I know questions regarding this have already been posted here, and OPs always ended up realizing that they've been creating a
State
after snapshot was taken and then reading from it in the said snapshot, triggering the expected error. But I have created a minimal working (actually crashing) example where I feel like the issue is not caused by this (or is it ?) I create a MutableState as a global variable, before any composition happen, and then never create any other state. So, no snapshot happened yet (right ?) I then read it in a
@Composable
and do a bunch of write from another thread -> This trigger the error. From my understanding, this should not cause any issue. Yes there will be inconsistency with the state with a bunch of conflicting snapshot writes and this is probably not a good pattern, but why is it crashing ? Code and more info in 🧵
Copy code
class SomeStateHolder {
    private val _items = mutableStateListOf<String>()
    val items: SnapshotStateList<String> = _items

    suspend fun addAndRemove(item: String) = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
        _items += item
        _items -= item
    }
}

val stateHolder = SomeStateHolder()

val spawnerFlow = flow {
    var counter = 0L
    while (true) {
        emit("Item ${counter++}")
        yield()
    }
}.flowOn(<http://Dispatchers.IO|Dispatchers.IO>)

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LaunchedEffect(spawnerFlow) {
                spawnerFlow.collect { item ->
                    launch {
                        stateHolder.addAndRemove(item)
                    }
                }
            }

            Column(modifier = Modifier.fillMaxSize()) {
                stateHolder.items.forEach { item ->
                    key(item) {
                        Text(text = item, color = Color.Red)
                    }
                }
            }
        }
    }
}
Interesting facts: • Removing the
launch {
in the
LaunchedEffect
seems to fix the issue • Not switching to
<http://Dispatchers.IO|Dispatchers.IO>
in
addAndRemove
also seems to fix the issue All of these + the fact that the crash is a bit random hint at a concurrency issue, but is it expected ? From my understanding, this scenario is supposed to be safe, what am I missing ?
s
Workaround: try to mutate your state in a mutable snapshot (Snapshot.withMutableSnapshot) I am not sure what /exactly/ is happening here, but likely there's a state where the snapshot system has no active records when reading state inside composition. I think it is worth filing a bug, although it might be WAI