# MutatorMutex

Artifact: [androidx.compose.foundation:foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation) [View Source](https://cs.android.com/search?q=file:androidx/compose/foundation/MutatorMutex.kt+class:androidx.compose.foundation.MutatorMutex) Added in [1.0.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.0.0) Common/AllAndroid/JVM
- Cmn

  ```
  class MutatorMutex
  ```

*** ** * ** ***

Mutual exclusion for UI state mutation over time.

`https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)` permits interruptible state mutation over time using a standard `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatePriority`. A `https://developer.android.com/reference/kotlin/androidx/compose/foundation/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 `https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html`, new attempts to `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)` the guarded state will either cancel the current mutator or if the current mutator has a higher priority, the new caller will throw `https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html`.

`https://developer.android.com/reference/kotlin/androidx/compose/foundation/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 `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex` instance should be hidden as an implementation detail. For example:

```kotlin
import androidx.compose.foundation.MutatorMutex
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope

@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")
        }
    }
}
```

## Summary

| ### Public constructors |
|---|---|
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#MutatorMutex()()` | Cmn |

| ### Public functions |
|---|---|---|
| `suspend R` | `<R : https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-any/index.html?> https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)(priority: https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatePriority, block: suspend () -> R)` Enforce that only a single caller may be active at a time. | Cmn |
| `suspend R` | `<T : https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-any/index.html?, R : https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-any/index.html?> https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)( receiver: T, priority: https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatePriority, block: suspend T.() -> R )` Enforce that only a single caller may be active at a time. | Cmn |
| `inline https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-boolean/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#tryMutate(kotlin.Function0)(block: () -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html)` Attempt to mutate synchronously if there is no other active caller. | Cmn |

## Public constructors

### MutatorMutex

Cmn Added in [1.0.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.0.0)

```
MutatorMutex()
```

## Public functions

### mutate

Cmn

```
suspend fun <R : Any?> mutate(
    priority: MutatePriority = MutatePriority.Default,
    block: suspend () -> R
): R
```

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

If `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)` is called while another call to `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)` or `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)` is in progress, their `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)` values are compared. If the new caller has a `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)` equal to or higher than the call in progress, the call in progress will be cancelled, throwing `https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html` and the new caller's `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)` will be invoked. If the call in progress had a higher `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)` than the new caller, the new caller will throw `https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html` without invoking `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)`.

| Parameters |
|---|---|
| `priority: https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatePriority = MutatePriority.Default` | the priority of this mutation; `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatePriority#Default` by default. Higher priority mutations will interrupt lower priority mutations. |
| `block: suspend () -> R` | mutation code to run mutually exclusive with any other call to `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)` or `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)`. |

### mutateWith

Cmn

```
suspend fun <T : Any?, R : Any?> mutateWith(
    receiver: T,
    priority: MutatePriority = MutatePriority.Default,
    block: suspend T.() -> R
): R
```

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

If `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)` is called while another call to `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)` or `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)` is in progress, their `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)` values are compared. If the new caller has a `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)` equal to or higher than the call in progress, the call in progress will be cancelled, throwing `https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html` and the new caller's `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)` will be invoked. If the call in progress had a higher `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)` than the new caller, the new caller will throw `https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html` without invoking `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)`.

This variant of `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)` calls its `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)` with a `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)`, 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 `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)` while leaving the rest of the state object read-only. For example:

```kotlin
import androidx.compose.foundation.MutatorMutex
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf

@Stable
class ScrollState(position: Int = 0) {
    private var _position = mutableStateOf(position)
    val position: Int by _position

    private val mutatorMutex = MutatorMutex()

    /** Only [block] in a call to [scroll] may change the value of [position]. */
    suspend fun <R> scroll(block: suspend MutableState<Int>.() -> R): R =
        mutatorMutex.mutateWith(_position, block = block)
}
```

| Parameters |
|---|---|
| `receiver: T` | the receiver `this` that `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)` will be called with |
| `priority: https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatePriority = MutatePriority.Default` | the priority of this mutation; `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatePriority#Default` by default. Higher priority mutations will interrupt lower priority mutations. |
| `block: suspend T.() -> R` | mutation code to run mutually exclusive with any other call to `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)` or `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)`. |

### tryMutate

Cmn Added in [1.6.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.6.0)

```
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 `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#tryMutate(kotlin.Function0)` 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 `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)` or `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)` call with an equal or higher mutation priority.

Calls to `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)` and `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)` will suspend until execution of the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#tryMutate(kotlin.Function0)` has finished.

| Parameters |
|---|---|
| `block: () -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | mutation code to run mutually exclusive with any other call to `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutate(androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction0)`, `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#mutateWith(kotlin.Any,androidx.compose.foundation.MutatePriority,kotlin.coroutines.SuspendFunction1)` or `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#tryMutate(kotlin.Function0)`. |

| Returns |
|---|---|
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-boolean/index.html` | true if the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#tryMutate(kotlin.Function0)` was executed, false if there was another active caller and the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/MutatorMutex#tryMutate(kotlin.Function0)` was not executed. |