SnapshotMutationPolicy


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

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

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.MutableState
import androidx.compose.runtime.SnapshotMutationPolicy
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshots.Snapshot

/**
 * 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 = Snapshot.takeMutableSnapshot()
val snapshot2 = Snapshot.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 functions

Boolean
equivalent(a: T, b: T)

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

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

Merge conflicting changes in snapshots.

Cmn

Public functions

equivalent

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.MutableState
import androidx.compose.runtime.SnapshotMutationPolicy
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.snapshots.Snapshot

/**
 * 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 = Snapshot.takeMutableSnapshot()
val snapshot2 = Snapshot.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.