Compose Animation Interview Questions
UI › Compose UI
Walk me through how you'd decide between animate*AsState and a raw Animatable when you need to animate a value in Compose.
What a strong answer covers: animate*AsState is for value-driven, one-shot transitions tied to a composable's own state: it's stateless from the caller's perspective, restarts automatically when the target changes, and can't be interrupted or sequenced beyond changing the target. Animatable is for imperative control: manual snapTo, chaining multiple animateTo calls in a coroutine, cancelling mid-flight for gesture-driven motion, or cases where the animation isn't purely a function of one composable's state. The trap is reaching for Animatable out of habit when animate*AsState would be simpler and already manages its own coroutine scope.
Several UI properties (size, color, elevation) need to animate together whenever a card's selected state flips. Why is stacking several animate*AsState calls worse here than updateTransition, and where does the difference actually bite?
What a strong answer covers: Independent animate*AsState calls each run on their own animation clock, so nothing guarantees they start, finish, or stay in sync frame to frame; you can end up with elevation finishing before color, which reads as uncoordinated jank. updateTransition (or rememberTransition) creates one Transition keyed off a single target state, so all child animateX values derived from it share the same state machine and finish together, and it exposes currentState/targetState for staggering. The nuance a weak answer misses: this also gives you one source of truth for whether a transition is still running, useful for disabling input mid-animation, instead of stitching together several isRunning checks.
Describe how you'd build a swipe-to-dismiss gesture backed by Animatable, including what happens if the user starts a new drag while the settle animation from the previous release is still running.
What a strong answer covers: Drag deltas call offsetX.snapTo(newValue) on every move so the value tracks the finger instantly with no animation, and on drag end you launch offsetX.animateTo(target, spring or decay) to settle to the dismissed position or back to zero based on velocity and threshold. The core trap is re-entrancy: Animatable enforces mutual exclusion internally, so calling snapTo or animateTo while a previous animateTo is in flight automatically cancels it, meaning you don't need to manually cancel a Job, but you do need the drag's coroutine scope to survive recomposition (rememberCoroutineScope) and to avoid two different callers racing animateTo calls on the same Animatable from separate scopes.