Hi folks. The simple code below is triggering `err...
# compose
t
Hi folks. The simple code below is triggering
error()
in some Android devices. I'm not expert in Derived State and not the code owner. But I'm curious about Compose Atomic operations related to Derived States. The reason to invoke
error()
is the
SnapshotStateList
empty. Is Compose Runtime scheduling two Operations
stack.clear()
and
stack += item
and running in sequence or is the derivedStateOf cache that is not atomic updated?
Copy code
import androidx.compose.runtime.toMutableStateList

public class ItemsManager<T>(items: List<T>) {
    private val stack: SnapshotStateList<T> = items.toMutableStateList()

    public val lastItemOrNull: T? by derivedStateOf {
        stack.lastOrNull()
    }

    public val lastItem: T by derivedStateOf {
        lastItemOrNull ?: error("expected one item at least")
    }

    public fun replaceAll(item: T) {
        stack.clear()
        stack += item
    }
}

@Composable
fun something() {
    val manager = remember { ItemsManager(items = listOf(1)) }
    val last = manager.lastItem

    Text(text = "$last is the last item")

    Button(onClick = { 
        manager.replaceAll(4)
    }) {
        Text(text = "Replace All")
    }
}
s
If you want to make two changes but only want to apply them together and never want to be in the "in-between" state, you can wrap them with
Snapshot.withMutableState { change1(); change2() }
So wrap everything in your replaceAll function with it.
t
Should I always mutate a
SnapshotStateList
using
withMutableState
to have an atomic operation?
s
If you are doing two or more changes, and you never want it to be possible to see the in-between state actually applied, then yes.
t
Thanks a lot. Tested and no more
error()
trigger. Even using delay like:
Copy code
public suspend fun replaceAll(item: T) {
        Snapshot.withMutableSnapshot {
            stack.clear()
            delay(1_000)
            stack += item
        }
    }
s
Yeah the changes are not applied at all until after the entire block is run. The docs on the function should explain this much better than I ever would.
👍 1
t
block
must not suspend if
withMutableSnapshot
is called from a suspend function.
Docs not recommend suspend block but I did just to test and worked.