Strong Skipping is an experimental mode added to the Compose compiler in version 1.5.4. With strong skipping enabled, the compiler changes its behavior in two ways:
Warning: We do not currently consider this mode production ready. We have enabled it in the Compose 1.7 alpha and will evaluate it before reaching beta.
Strong skipping mode relaxes some of the stability rules normally applied by the Compose compiler when it comes to skipping and composable functions. By default, the Compose compiler will mark a composable function as skippable if it only has stable values provided as arguments. Strong skipping mode changes this.
With strong skipping enabled, all restartable composable functions will be skippable, regardless of if they have unstable parameters or not. Non-restartable composable functions remain unskippable.
To determine whether to skip a composable during recomposition, unstable parameters are compared with their previous values using instance equality. Stable parameters continue to be compared with their previous values using object equality - Object.equals()
.
If all parameters meet these requirements, the composable is skipped during recomposition.
If you want to opt out a composable function from strong skipping, i.e. you want a restartable but non-skippable composable, you can use the @NonSkippableComposable
annotation.
@NonSkippableComposable @Composable fun MyNonSkippableComposable {}
@Stable
?You will still need to annotate a class with @Stable
if you want the object compared with object equality instead of instance equality.
Strong skipping mode also enables more aggressive memoization of lambdas inside composable functions. By default, the Compose compiler will memoize lambdas in composable functions that only capture stable values, additionally composable lambdas are always memoized.
Note: Lambdas with no captures are also memoized, however this is done by the Kotlin compiler and not by the Compose compiler plugin.
With strong skipping enabled, lambdas with unstable captures are also memoized.
Effectively this is wrapping your lambda with a remember
call, keyed with the captures of the lambda, automatically e.g.
@Composable fun MyComposable(unstableObject: Unstable, stableObject: Stable) { val lambda = { use(unstableObject) use(stableObject) } }
roughly becomes the following with strong skipping enabled
@Composable fun MyComposable(unstableObject: Unstable, stableObject: Stable) { val lambda = remember(unstableObject, stableObject) { { use(unstableObject) use(stableObject) } } }
The keys follow the same comparison rules as composable functions, unstable keys are compared using instance equality and stable keys are compared using object equality.
Note: This is slightly different to a normal
remember
call where all keys are compared using object equality.
Doing this optimization greatly increases the number of composables that will skip during recomposition as without this memoization, any composable that takes a lambda parameter will most likely have a new lambda allocated during recomposition and therefore will not have equal parameters to the last composition.
Note: A common misconception is that lambdas with unstable captures are themselves unstable objects. This is not true, lambdas are always considered stable, however as they were not memoized and reallocated during recomposition, they lead to composables that were not skipped due to unequal parameters.
If you have a lambda that you do not want memoized, you can use the @DontMemoize
annotation.
val lambda = @DontMemoize { ... }
We have enabled this option in AndroidX for all Compose modules.
To strong skipping for a gradle module, include:
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>() { compilerOptions.freeCompilerArgs.addAll( "-P", "plugin:androidx.compose.compiler.plugins.kotlin:featureFlag=StrongSkipping", ) }