Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5fbfc168 authored by Omar Miatello's avatar Omar Miatello
Browse files

Annotate MotionValue properties with @FrequentlyChangingValue

This change adds the `@FrequentlyChangingValue` annotation to all
properties of `MotionValue` that update at a high frequency (e.g., on
every frame).

`MotionValue` properties like `.output`, `.isStable`, and `.segmentKey`
are derived from continuous sources like user input.
Reading these values directly within a composable function creates a
state dependency that triggers recomposition on every update, leading to
significant performance issues and UI jank.

By annotating these properties, we provide a clear signal to the tooling
and developers about their volatile nature. This will trigger a lint
warning when these properties are read during composition, guiding
developers to avoid this anti-pattern and instead pass the
`MotionValue` instance directly to modifiers.
This approach was chosen over alternatives like `derivedStateOf()`,
which was previously found to have its own unacceptable performance
overhead for this specific use case.

Test: Manually verified lint warnings appear when reading annotated
properties in a Composable.
Bug: 441041846
Flag: com.android.systemui.scene_container

Change-Id: Ib455a53ed5c4d7f0c459448f661c29cd33c28472
parent 2b309391
Loading
Loading
Loading
Loading
+15 −6
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.mechanics

import androidx.compose.runtime.FloatState
import androidx.compose.runtime.annotation.FrequentlyChangingValue
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
@@ -139,10 +140,11 @@ class MotionValue(
        )

    /** The [MotionSpec] describing the mapping of this [MotionValue]'s input to the output. */
    val spec: MotionSpec by impl::spec
    // TODO(b/441041846): This should not change frequently
    @get:FrequentlyChangingValue val spec: MotionSpec by impl::spec

    /** Animated [output] value. */
    val output: Float by impl::output
    @get:FrequentlyChangingValue val output: Float by impl::output

    /**
     * [output] value, but without animations.
@@ -151,13 +153,15 @@ class MotionValue(
     *
     * While [isStable], [outputTarget] and [output] are the same value.
     */
    val outputTarget: Float by impl::outputTarget
    // TODO(b/441041846): This should not change frequently
    @get:FrequentlyChangingValue val outputTarget: Float by impl::outputTarget

    /** The [output] exposed as [FloatState]. */
    override val floatValue: Float by impl::output
    @get:FrequentlyChangingValue override val floatValue: Float by impl::output

    /** Whether an animation is currently running. */
    val isStable: Boolean by impl::isStable
    // TODO(b/441041846): This should not change frequently
    @get:FrequentlyChangingValue val isStable: Boolean by impl::isStable

    /**
     * Whether the output can change its value.
@@ -167,18 +171,23 @@ class MotionValue(
     * output is guaranteed not to change unless the [spec] or the input (enough to change segments)
     * changes. This can be used to avoid unnecessary work like recomposition or re-measurement.
     */
    val isOutputFixed: Boolean by impl::isOutputFixed
    // TODO(b/441041846): This should not change frequently
    @get:FrequentlyChangingValue val isOutputFixed: Boolean by impl::isOutputFixed

    /**
     * The current value for the [SemanticKey].
     *
     * `null` if not defined in the spec.
     */
    // TODO(b/441041846): This should not change frequently
    @FrequentlyChangingValue
    operator fun <T> get(key: SemanticKey<T>): T? {
        return impl.semanticState(key)
    }

    /** The current segment used to compute the output. */
    // TODO(b/441041846): This should not change frequently
    @get:FrequentlyChangingValue
    val segmentKey: SegmentKey
        get() = impl.currentComputedValues.segment.key