One Off the Slack: How Do We Identify the Source of AnimatedContent Jank?

Chris Hatton asked:

Do folk have any smart ways of discovering what causes a recomposition?

My ideal would be some log that says *‘Because state changed at '*?

My use case is debugging ‘jank’ in AnimatedContent transitions because multiple recompositions are firing where conceptually there should be only one.

Carefully tracing the code for state changes is a given, just wondering if people can share any smart breakpoints/profiling tricks they’re employing?

@Composable
fun MyView(viewModel: MyViewModel) {

    val workflowState by viewModel.stateFlow.collectAsState()

    AnimatedContent(
        targetState = workflowState,
        transitionSpec = {
            when (initialState transitionTo targetState) {
                SlideLeft -> slideInHorizontally({ width -> width }) + fadeIn() with slideOutHorizontally({ width -> -width }) + fadeOut()
                SlideRight -> slideInHorizontally({ width -> -width }) + fadeIn() with slideOutHorizontally({ width -> width }) + fadeOut()
                None -> EnterTransition.None with ExitTransition.None
            }
        },
        modifier = Modifier.fillMaxSize()
    ) {
        with(workflowState) {
            when (this) {

transitionTo is my own infix function to determine what kind of transition should be used; either SlideLeft or SlideRight if the workflow state represents another ‘screen’, or None if the new state represents a state change within the current ‘screen’.

Google’s Doris Liu suggested replacing with(workflowState) with with(it)… and it improved matters. Doris went on to explain:

There are multiple sets of content that AnimatedContent is transitioning in between, each is associated with a different target state. Each set of the content ideally should only reflect what’s defined in their corresponding target state. That would require that the content read the state passed to the content lambda.

By reading an external state (i.e. workflowState), all the active content is observing that change, therefore causing unnecessary recomposition

Chris then summed up:

Yes, I think I see now, even though it’s the same state, reaching across scope to effectively learn the same thing twice caused double composition.


Read the original thread in the kotlinlang Slack workspace. Not a member? Join that Slack workspace here!