Google is committed to advancing racial equity for Black communities. See how.

SnapshotMutationPolicy

interface SnapshotMutationPolicy<T>
androidx.compose.runtime.SnapshotMutationPolicy

A policy to control how the result of mutableStateOf and state report and merge changes to the state object.

A mutation policy can be passed as an parameter to state, mutableStateOf, and ambientOf.

Typically, one of the stock policies should be used such as referentialEqualityPolicy, structuralEqualityPolicy, or neverEqualPolicy. However, a custom mutation policy can be created by implementing this interface, such as a counter policy,

import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshots.takeMutableSnapshot

/**
 * A policy that treats an `MutableState<Int>` as a counter. Changing the value to the same
 * integer value will not be considered a change. When snapshots are applied the changes made by
 * the applying snapshot are added together with changes of other snapshots. Changes to a
 * [MutableState] with a counterPolicy will never cause an apply conflict.
 *
 * As the name implies, this is useful when counting things, such as tracking the amount of
 * a resource consumed or produced while in a snapshot. For example, if snapshot A produces 10
 * things and snapshot B produces 20 things, the result of applying both A and B should be that
 * 30 things were produced.
 */
fun counterPolicy(): SnapshotMutationPolicy<Int> = object : SnapshotMutationPolicy<Int> {
    override fun equivalent(a: Int, b: Int): Boolean = a == b
    override fun merge(previous: Int, current: Int, applied: Int) =
        current + (applied - previous)
}

val state = mutableStateOf(0, counterPolicy())
val snapshot1 = takeMutableSnapshot()
val snapshot2 = takeMutableSnapshot()
try {
    snapshot1.enter { state.value += 10 }
    snapshot2.enter { state.value += 20 }
    snapshot1.apply().check()
    snapshot2.apply().check()
} finally {
    snapshot1.dispose()
    snapshot2.dispose()
}

// State is now equals 30 as the changes made in the snapshots are added together.

Summary

Public methods
abstract Boolean
equivalent(a: T, b: T)

Determine if setting a state value's are equivalent and should be treated as equal.

open T?
merge(previous: T, current: T, applied: T)

Merge conflicting changes in snapshots.

Public methods

equivalent

abstract fun equivalent(
    a: T,
    b: T
): Boolean

Determine if setting a state value's are equivalent and should be treated as equal. If equivalent returns true the new value is not considered a change.

merge

open fun merge(
    previous: T,
    current: T,
    applied: T
): T?

Merge conflicting changes in snapshots. This is only called if current and applied are not equivalent. If a valid merged value can be calculated then it should be returned.

For example, if the state object holds an immutable data class with multiple fields, and applied has changed fields that are unmodified by current it might be valid to return a new copy of the data class that combines that changes from both current and applied allowing a snapshot to apply that would have otherwise failed.

import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshots.takeMutableSnapshot

/**
 * A policy that treats an `MutableState<Int>` as a counter. Changing the value to the same
 * integer value will not be considered a change. When snapshots are applied the changes made by
 * the applying snapshot are added together with changes of other snapshots. Changes to a
 * [MutableState] with a counterPolicy will never cause an apply conflict.
 *
 * As the name implies, this is useful when counting things, such as tracking the amount of
 * a resource consumed or produced while in a snapshot. For example, if snapshot A produces 10
 * things and snapshot B produces 20 things, the result of applying both A and B should be that
 * 30 things were produced.
 */
fun counterPolicy(): SnapshotMutationPolicy<Int> = object : SnapshotMutationPolicy<Int> {
    override fun equivalent(a: Int, b: Int): Boolean = a == b
    override fun merge(previous: Int, current: Int, applied: Int) =
        current + (applied - previous)
}

val state = mutableStateOf(0, counterPolicy())
val snapshot1 = takeMutableSnapshot()
val snapshot2 = takeMutableSnapshot()
try {
    snapshot1.enter { state.value += 10 }
    snapshot2.enter { state.value += 20 }
    snapshot1.apply().check()
    snapshot2.apply().check()
} finally {
    snapshot1.dispose()
    snapshot2.dispose()
}

// State is now equals 30 as the changes made in the snapshots are added together.