---
title: "MutatorMutex"
description: "Mutual exclusion for UI state mutation over time.

[mutate] permits interruptible state mutation over time using a standard [MutatePriority]. A
[MutatorMutex] enforces that only a single writer can be active at a time for a particular state
resource. Instead of queueing callers that would acquire the lock like a traditional [Mutex], new
attempts to [mutate] the guarded state will either cancel the current mutator or if the current
mutator has a higher priority, the new caller will throw [CancellationException].

[MutatorMutex] should be used for implementing hoisted state objects that many mutators may want
to manipulate over time such that those mutators can coordinate with one another. The
[MutatorMutex] instance should be hidden as an implementation detail. For example:"
type: "class"
---

<div class='type'>Class</div>


<a id='references'></a>

<div class='sourceset sourceset-common'>Common</div>


```kotlin
class MutatorMutex
```


Mutual exclusion for UI state mutation over time.

`mutate` permits interruptible state mutation over time using a standard `MutatePriority`. A
`MutatorMutex` enforces that only a single writer can be active at a time for a particular state
resource. Instead of queueing callers that would acquire the lock like a traditional `Mutex`, new
attempts to `mutate` the guarded state will either cancel the current mutator or if the current
mutator has a higher priority, the new caller will throw `CancellationException`.

`MutatorMutex` should be used for implementing hoisted state objects that many mutators may want
to manipulate over time such that those mutators can coordinate with one another. The
`MutatorMutex` instance should be hidden as an implementation detail. For example:


## Functions

```kotlin
suspend fun <R> mutate(
        priority: MutatePriority = MutatePriority.Default,
        block: suspend () -> R,
    ) = coroutineScope {
        val mutator = Mutator(priority, coroutineContext[Job]!!)

        tryMutateOrCancel(mutator)

        mutex.withLock {
            try {
                block()
            } finally {
                currentMutator.compareAndSet(mutator, null)
            }
        }
    }
```


Enforce that only a single caller may be active at a time.

If `mutate` is called while another call to `mutate` or `mutateWith` is in progress, their
`priority` values are compared. If the new caller has a `priority` equal to or higher than
the call in progress, the call in progress will be cancelled, throwing
`CancellationException` and the new caller's `block` will be invoked. If the call in progress
had a higher `priority` than the new caller, the new caller will throw
`CancellationException` without invoking `block`.

#### Parameters

| | |
| --- | --- |
| priority | the priority of this mutation; `MutatePriority.Default` by default. Higher priority mutations will interrupt lower priority mutations. |
| block | mutation code to run mutually exclusive with any other call to `mutate` or `mutateWith`. |



```kotlin
suspend fun <T, R> mutateWith(
        receiver: T,
        priority: MutatePriority = MutatePriority.Default,
        block: suspend T.() -> R,
    ) = coroutineScope {
        val mutator = Mutator(priority, coroutineContext[Job]!!)

        tryMutateOrCancel(mutator)

        mutex.withLock {
            try {
                receiver.block()
            } finally {
                currentMutator.compareAndSet(mutator, null)
            }
        }
    }
```


Enforce that only a single caller may be active at a time.

If `mutateWith` is called while another call to `mutate` or `mutateWith` is in progress,
their `priority` values are compared. If the new caller has a `priority` equal to or higher
than the call in progress, the call in progress will be cancelled, throwing
`CancellationException` and the new caller's `block` will be invoked. If the call in progress
had a higher `priority` than the new caller, the new caller will throw
`CancellationException` without invoking `block`.

This variant of `mutate` calls its `block` with a `receiver`, removing the need to create an
additional capturing lambda to invoke it with a receiver object. This can be used to expose a
mutable scope to the provided `block` while leaving the rest of the state object read-only.
For example:

#### Parameters

| | |
| --- | --- |
| receiver | the receiver `this` that `block` will be called with |
| priority | the priority of this mutation; `MutatePriority.Default` by default. Higher priority mutations will interrupt lower priority mutations. |
| block | mutation code to run mutually exclusive with any other call to `mutate` or `mutateWith`. |



```kotlin
inline fun tryMutate(block: () -> Unit): Boolean
```


Attempt to mutate synchronously if there is no other active caller. If there is no other
active caller, the `block` will be executed in a lock. If there is another active caller,
this method will return false, indicating that the active caller needs to be cancelled
through a `mutate` or `mutateWith` call with an equal or higher mutation priority.

Calls to `mutate` and `mutateWith` will suspend until execution of the `block` has finished.

#### Parameters

| | |
| --- | --- |
| block | mutation code to run mutually exclusive with any other call to `mutate`, `mutateWith` or `tryMutate`. |


#### Returns

| | |
| --- | --- |
|  | true if the `block` was executed, false if there was another active caller and the `block` was not executed. |




## Code Examples

### mutatorMutexStateObject
```kotlin
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")
            }
        }
    }
}
```

