# InvalidationStrategy

Artifact: [androidx.constraintlayout:constraintlayout-compose](https://developer.android.com/jetpack/androidx/releases/constraintlayout) [View Source](https://cs.android.com/search?q=file:androidx/constraintlayout/compose/MotionLayout.kt+class:androidx.constraintlayout.compose.InvalidationStrategy) Added in [1.1.2](https://developer.android.com/jetpack/androidx/releases/constraintlayout#1.1.2)

<br />

```
class InvalidationStrategy
```

<br />

*** ** * ** ***

Provide different invalidation strategies for `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)`.

Whenever `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)` needs invalidating, it has to recalculate all animations based on the current state at the measure pass, this is the slowest process in the `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)` cycle.

An invalidation can be triggered by two reasons:

- Incoming fixed size constraints have changed. This is necessary since layouts are highly dependent on their available space, it'll typically happen if you are externally animating the dimensions of `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)`.

- The content of MotionLayout recomposes. This is necessary since Layouts in Compose don't know the reason for a new measure pass, so we need to recalculate animations even if recomposition didn't affect the actual Layout. For example, this **definitely** happens if you are using `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayoutScope#customProperties(kotlin.String)`, even when you are just animating a background color, the custom property will trigger a recomposition in the content and `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)` will be forced to invalidate since it cannot know that the Layout was not affected.

So, you may use `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategy` to help `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)` decide when to invalidate:

- `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategy#onObservedStateChange()`: Mitigates invalidation from content recomposition by explicitly reading the State variables you want to cause invalidation. You'll likely want to apply this strategy to most of your `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)` Composables. As, in the most simple cases you can just provide an empty lambda. Here's a full example:

```kotlin
val progress = remember { Animatable(0f) }

MotionLayout(
    motionScene = remember {
        // A simple MotionScene that animates a background color from Red to Blue
        MotionScene {
            val (textRef) = createRefsFor("text")

            val start = constraintSet {
                constrain(textRef) {
                    centerTo(parent)
                    customColor("background", Color.Red)
                }
            }
            val end = constraintSet(extendConstraintSet = start) {
                constrain(textRef) {
                    customColor("background", Color.Blue)
                }
            }
            defaultTransition(from = start, to = end)
        }
    },
    progress = progress.value,
    modifier = Modifier.fillMaxSize(),
    invalidationStrategy = remember {
        InvalidationStrategy(
            onObservedStateChange = { /* Empty, no need to invalidate on content recomposition */ }
        )
    }
) {
    // The content doesn't depend on any State variable that may affect the Layout's measure result
    Text(
        text = "Hello, World",
        modifier = Modifier
            .layoutId("text")
            // However, the custom color is causing recomposition on each animated frame
            .background(customColor("text", "background"))
    )
}
LaunchedEffect(Unit) {
    delay(1000)
    progress.animateTo(targetValue = 1f, tween(durationMillis = 1200))
}
```

*When should I provide States to read then?*

Whenever a State backed variable that affects the Layout's measure result changes. The most common cases are Strings on the Text Composable.

Here's an example where the text changes half-way through the animation:

```kotlin
val progress = remember { Animatable(0f) }

var textString by remember { mutableStateOf("Hello, World") }
MotionLayout(
    motionScene = remember {
        // A MotionScene that animates a Text from one corner to the other with an animated
        // background color
        MotionScene {
            val (textRef) = createRefsFor("text")

            defaultTransition(
                from = constraintSet {
                    constrain(textRef) {
                        top.linkTo(parent.top)
                        start.linkTo(parent.start)

                        customColor("background", Color.LightGray)
                    }
                },
                to = constraintSet {
                    constrain(textRef) {
                        bottom.linkTo(parent.bottom)
                        end.linkTo(parent.end)

                        customColor("background", Color.Gray)
                    }
                }
            )
        }
    },
    progress = progress.value,
    modifier = Modifier.fillMaxSize(),
    invalidationStrategy = remember {
        InvalidationStrategy(
            onObservedStateChange = @Suppress("UNUSED_EXPRESSION"){
                // We read our State String variable in this block, to guarantee that
                // MotionLayout will invalidate to accommodate the new Text Layout.
                // Note that we do not read the custom color here since it doesn't affect the Layout
                textString
            }
        )
    }
) {
    // The text Layout will change based on the provided State String
    Text(
        text = textString,
        modifier = Modifier
            .layoutId("text")
            // Without an invalidation strategy, the custom color would normally invalidate
            // MotionLayout due to recomposition
            .background(customColor("text", "background"))
    )
}
LaunchedEffect(Unit) {
    delay(1000)
    progress.animateTo(targetValue = 1f, tween(durationMillis = 3000)) {
        if (value >= 0.5f) {
            textString = "This is a\n" + "significantly different text."
        }
    }
}
```

*What if my Text changes continuously?*

There's a few strategies you can take depending on how you expect the Text to behave.

For example, if you don't expect the text to need more than one line, you can set the Text with `softWrap = false` and `overflow = TextOverflow.Visible`:

```kotlin
MotionLayout(
    motionScene = motionScene,
    progress = progress,
    modifier = Modifier.size(200.dp),
    invalidationStrategy = remember { InvalidationStrategy { /* Do not invalidate on content recomposition */ } }
) {
    Text(
        text = <your-State-String>,
        modifier = Modifier.layoutId("text"),
        softWrap = false,
        overflow = TextOverflow.Visible
    )
}
```

The Text layout won't change significantly and performance will be much improved.

- `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategy#onIncomingConstraints()`: With this lambda you can mitigate invalidation from incoming constraints. You'll only have to worry about providing this lambda if you or the Layout you're using is animating measuring constraints on `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)`. If the size is only changing in specific, discrete values, then you should allow `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)` to invalidate normally.

Here's an example where we manually animate `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)`'s size through a Modifier (along with the MotionLayout animation), and shows how to mitigate invalidation by rate-limiting:

```kotlin
val textId = "text"
val progress = remember { Animatable(0f) }

val initial = remember { DpSize(100.dp, 100.dp) }
val target = remember { DpSize(120.dp, 200.dp) }
var size by remember { mutableStateOf(initial) }

MotionLayout(
    motionScene = remember {
        MotionScene {
            val (textRef) = createRefsFor( "text")

            // Animate text from the bottom of the layout to the top
            defaultTransition(
                from = constraintSet {
                    constrain(textRef) {
                        centerHorizontallyTo(parent)
                        bottom.linkTo(parent.bottom)
                    }
                },
                to = constraintSet {
                    constrain(textRef) {
                        centerHorizontallyTo(parent)
                        top.linkTo(parent.top)
                    }
                }
            )
        }
    },
    progress = progress.value,
    modifier = Modifier.background(Color.Cyan).size(size),
    invalidationStrategy = remember {
        InvalidationStrategy(
            onIncomingConstraints = { old, new ->
                // We invalidate every third frame, or when the change is higher than 5 pixels
                shouldInvalidateOnFixedWidth(old, new, skipCount = 3, threshold = 5) ||
                    shouldInvalidateOnFixedHeight(old, new, skipCount = 3, threshold = 5)
            },
            // No need to worry about content state changes for this example
            onObservedStateChange = {}
        )
    }
) {
    Text("Hello, World!", Modifier.layoutId(textId))
}

// Animate the size along with the MotionLayout. Without an invalidation strategy, this will cause
// MotionLayout to invalidate at every measure pass since it's getting fixed size Constraints at
// different values
LaunchedEffect(Unit) {
    val sizeDifference = target - initial
    delay(1000)
    progress.animateTo(1f, tween(1200)) {
        size = initial + (sizeDifference * value)
    }
}
```

Note that `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategySpecification#shouldInvalidateOnFixedWidth(androidx.compose.ui.unit.Constraints,androidx.compose.ui.unit.Constraints,kotlin.Int,kotlin.Int)` and `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategySpecification#shouldInvalidateOnFixedHeight(androidx.compose.ui.unit.Constraints,androidx.compose.ui.unit.Constraints,kotlin.Int,kotlin.Int)` are helper methods available in `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategySpecification`.

An alternative to rate-limiting is to "simply" avoid invalidation from changed fixed size constraints. This can be done by leaving `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)` as wrap content and then have it choose its own start and ending size. Naturally, this is not always feasible, specially if it's a parent Composable the one that's animating the size constraints.

But, here's the MotionScene showing how to achieve this behavior based on the example above:

```kotlin
MotionScene {
    // We'll use fakeParentRef to choose our starting and ending size then constrain everything
    // else to it. MotionLayout will animate without invalidating.
    // There's no need to bind "fakeParent" to any actual Composable.
    val (fakeParentRef, textRef) = createRefsFor("fakeParent", "text")

    defaultTransition(
        from = constraintSet {
            constrain(fakeParentRef) {
                width = 100.dp.asDimension()
                height = 100.dp.asDimension()
            }

            constrain(textRef) {
                bottom.linkTo(fakeParentRef.bottom)
            }
        },
        to = constraintSet {
            constrain(fakeParentRef) {
                width = 120.dp.asDimension()
                height = 200.dp.asDimension()
            }

            constrain(textRef) {
                top.linkTo(fakeParentRef.top)
            }
        }
    )
}
```

You can then remove the size modifier and the invalidation strategy for `onIncomingConstraints`, as `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)` will animate through both sizes without invalidating.

| See also |
|---|---|
| `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategy#DefaultInvalidationStrategy()` |   |
| `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategySpecification` |   |
| `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategySpecification#shouldInvalidateOnFixedWidth(androidx.compose.ui.unit.Constraints,androidx.compose.ui.unit.Constraints,kotlin.Int,kotlin.Int)` |   |
| `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategySpecification#shouldInvalidateOnFixedHeight(androidx.compose.ui.unit.Constraints,androidx.compose.ui.unit.Constraints,kotlin.Int,kotlin.Int)` |   |

## Summary

| ### Public companion properties |
|---|---|
| `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategy` | `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategy#DefaultInvalidationStrategy()` Default invalidation strategy for `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)`. |

| ### Public constructors |
|---|
| `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategy#InvalidationStrategy(kotlin.Function3,kotlin.Function0)( onIncomingConstraints: (https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategySpecification.(old: https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Constraints, new: https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Constraints) -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-boolean/index.html)?, onObservedStateChange: (() -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html)? )` |

| ### Public properties |
|---|---|
| `(https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategySpecification.(old: https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Constraints, new: https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Constraints) -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-boolean/index.html)?` | `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategy#onIncomingConstraints()` Lambda to implement invalidation based on incoming `https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Constraints`. |
| `(() -> https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html)?` | `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategy#onObservedStateChange()` Lambda to implement invalidation on observed State changes. |

## Public companion properties

### DefaultInvalidationStrategy

Added in [1.1.2](https://developer.android.com/jetpack/androidx/releases/constraintlayout#1.1.2)

```
val DefaultInvalidationStrategy: InvalidationStrategy
```

Default invalidation strategy for `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)`.

This will cause it to invalidate whenever its content recomposes or when it receives different fixed size `https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Constraints` at the measure pass.

## Public constructors

### InvalidationStrategy

Added in [1.1.2](https://developer.android.com/jetpack/androidx/releases/constraintlayout#1.1.2)

```
InvalidationStrategy(
    onIncomingConstraints: (InvalidationStrategySpecification.(old: Constraints, new: Constraints) -> Boolean)? = null,
    onObservedStateChange: (() -> Unit)?
)
```

## Public properties

### onIncomingConstraints

Added in [1.1.2](https://developer.android.com/jetpack/androidx/releases/constraintlayout#1.1.2)

```
val onIncomingConstraints: (InvalidationStrategySpecification.(old: Constraints, new: Constraints) -> Boolean)?
```

Lambda to implement invalidation based on incoming `https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Constraints`.

Called every measure pass after the first measure (to obtain "old" `https://developer.android.com/reference/kotlin/androidx/compose/ui/unit/Constraints`), return `true` to indicate when to invalidate `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)`. The default behavior, would be to always return `false`.

See the documentation on `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategy` or either of `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategySpecification#shouldInvalidateOnFixedWidth(androidx.compose.ui.unit.Constraints,androidx.compose.ui.unit.Constraints,kotlin.Int,kotlin.Int)` /`https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategySpecification#shouldInvalidateOnFixedHeight(androidx.compose.ui.unit.Constraints,androidx.compose.ui.unit.Constraints,kotlin.Int,kotlin.Int)` to learn some strategies on how to improve invalidation due to incoming constraints.

### onObservedStateChange

Added in [1.1.2](https://developer.android.com/jetpack/androidx/releases/constraintlayout#1.1.2)

```
val onObservedStateChange: (() -> Unit)?
```

Lambda to implement invalidation on observed State changes.

`https://developer.android.com/reference/kotlin/androidx/compose/runtime/State` based variables should be read in the block of this lambda to have `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/MotionLayout.composable#MotionLayout(androidx.constraintlayout.compose.ConstraintSet,androidx.constraintlayout.compose.ConstraintSet,kotlin.Float,androidx.compose.ui.Modifier,androidx.constraintlayout.compose.Transition,androidx.constraintlayout.compose.DebugFlags,kotlin.Int,androidx.constraintlayout.compose.InvalidationStrategy,kotlin.Function1)` invalidate whenever any of those variables have changed.

You may use an assigned value or delegated variable for this purpose:

```kotlin
val stateVar0 = remember { mutableStateOf("Foo") }
var stateVar1 by remember { mutableStateOf("Bar") }
val invalidationStrategy = remember {
    InvalidationStrategy(
        onObservedStateChange = @Suppress("UNUSED_EXPRESSION") {
            stateVar0.value
            stateVar1
        }
    )
}
```

See `https://developer.android.com/reference/kotlin/androidx/constraintlayout/compose/InvalidationStrategy` to learn more about common strategies regarding invalidation on onObservedStateChange.