Class

MutatorMutex

Mutual exclusion for UI state mutation over time.

mutatorMutexStateObject

fun mutatorMutexStateObject() {
    @Stable
    class ScrollState(position: Int = 0) {
        private var _position by mutableStateOf(position)
        var position: Int
            get() = _position.coerceAtMost(range)
            set(value) {
                _position = value.coerceIn(0, range)
            }
        private var _range by mutableStateOf(0)
        var range: Int
            get() = _range
            set(value) {
                _range = value.coerceAtLeast(0)
            }
        var isScrolling by mutableStateOf(false)
            private set
        private val mutatorMutex = MutatorMutex()
        /** Only one caller to [scroll] can be in progress at a time. */
        suspend fun <R> scroll(block: suspend () -> R): R =
            mutatorMutex.mutate {
                isScrolling = true
                try {
                    block()
                } finally {
                    // MutatorMutex.mutate ensures mutual exclusion between blocks.
                    // By setting back to false in the finally block inside mutate, we ensure that
                    // we
                    // reset the state upon cancellation before the next block starts to run (if
                    // any).
                    isScrolling = false
                }
            }
    }
    /** Arbitrary animations can be defined as extensions using only public API */
    suspend fun ScrollState.animateTo(target: Int) {
        scroll { animate(from = position, to = target) { newPosition -> position = newPosition } }
    }
    /**
     * Presents two buttons for animating a scroll to the beginning or end of content. Pressing one
     * will cancel any current animation in progress.
     */
    @Composable
    fun ScrollControls(scrollState: ScrollState) {
        Row {
            val scope = rememberCoroutineScope()
            Button(onClick = { scope.launch { scrollState.animateTo(0) } }) {
                Text("Scroll to beginning")
            }
            Button(onClick = { scope.launch { scrollState.animateTo(scrollState.range) } }) {
                Text("Scroll to end")
            }
        }
    }
}