Having some issue getting a transition to animate,...
# compose
b
Having some issue getting a transition to animate, row has two main states - one with a full width button and one with two and a spacer. However the duration of the transition seems to be instantaneous and doesnt animate as I'd expect. Is there something Im missing? (Code in thread)
🧵 3
Row:
Copy code
@Composable
private fun ButtonRow(
    constraints: BoxWithConstraintsScope,
    downloadState: DownloadState,
    actioner: (AppDetailsAction) -> Unit
) {
    Row(modifier = Modifier.fillMaxWidth()) {
        val transitionData =
            updateTransitionData(constraints = constraints, downloadState = downloadState)
        when (downloadState) {
            Queued -> {
                CancelButton(
                    modifier = Modifier.height(transitionData.leftButtonHeight)
                        .width(transitionData.leftButtonWidth),
                    actioner = actioner
                )
                Spacer(modifier = Modifier.padding(transitionData.spacerWidth))
                QueuedButton(modifier = Modifier.width(transitionData.rightButtonWidth))
            }
            NotInstalled -> {
                CancelButton(
                    modifier = Modifier.height(transitionData.leftButtonHeight)
                        .width(transitionData.leftButtonWidth),
                    enabled = false,
                    actioner = actioner
                )
                Spacer(modifier = Modifier.padding(transitionData.spacerWidth))
                InstallButton(modifier = Modifier.width(transitionData.rightButtonWidth), actioner = actioner)
            }
       }
    }
}
🙏 1
Transition spec:
Copy code
I'm specifying the transition as:
private class TransitionData(
    leftButtonHeight: State<Dp>,
    leftButtonWidth: State<Dp>,
    rightButtonWidth: State<Dp>,
    spacerWidth: State<Dp>,
) {
    val leftButtonHeight by leftButtonHeight
    val leftButtonWidth by leftButtonWidth
    val rightButtonWidth by rightButtonWidth
    val spacerWidth by spacerWidth
}

@Composable
private fun updateTransitionData(
    constraints: BoxWithConstraintsScope,
    downloadState: DownloadState
): TransitionData {
    val spacerSize: Dp = 16.dp
    val buttonWidth = (constraints.maxWidth / 2) - spacerSize

    val transition = updateTransition(targetState = downloadState, label = "transition")

    val leftButtonHeight = transition.animateDp(
        transitionSpec = { tween(1000) },
        label = "leftHeight"
    ) { state ->
        when (state) {
            NotInstalled -> 0.dp
            Queued -> ButtonDefaults.MinHeight
        }
    }

    val leftButtonWidth = transition.animateDp(
        transitionSpec = { tween(1000) },
        label = "leftWidth"
    ) { state ->
        when (state) {
            NotInstalled -> 0.dp
            Queued -> buttonWidth
        }
    }

    val rightButtonWidth = transition.animateDp(
        transitionSpec = { tween(1000) },
        label = "rightWidth"
    ) { state ->
        when (state) {
            NotInstalled -> constraints.maxWidth
            Queued -> buttonWidth
        }
    }

    val spacerWidth = transition.animateDp(
        transitionSpec = { tween(1000) },
        label = "spacer") { state ->
        when (state) {
            NotInstalled -> 0.dp
            Queued -> spacerSize
        }
    }

    return remember(transition) { TransitionData(leftButtonHeight, leftButtonWidth, rightButtonWidth, spacerWidth) }
}
Buttons:
Copy code
@Composable
private fun CancelButton(
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    actioner: (AppDetailsAction) -> Unit
) {
    ThemedButton(
        modifier = modifier,
        enabled = enabled,
        outlined = true,
        onClick = { actioner(AppDetailsAction.CancelDownload) }
    ) {
        Text(text = stringResource(id = android.R.string.cancel))
    }
}

@Composable
private fun InstallButton(
    modifier: Modifier = Modifier,
    actioner: (AppDetailsAction) -> Unit
) {
    ThemedButton(
        modifier = modifier,
        onClick = { actioner(AppDetailsAction.Install) }
    ) {
        Text(text = stringResource(id = R.string.install))
    }
}

@Composable
private fun QueuedButton(
    modifier: Modifier = Modifier,
) {
    ThemedButton(
        modifier = modifier,
        enabled = false, 
        onClick = { }
    ) {
        Text(text = stringResource(id = R.string.queued))
    }
}
h
I'm on mobile so excuse my formatting. I feel like your problem might be at the end of
updateTransitionData
. It is being remembered as long as transition object is the same. I'm not sure if
updateTransition
had an internal remember or you are supposed to remember it but somethings might be going wrong there. Just a suspicion.
b
Interesting I'll check it out.
h
Oh forget what I've said. You are already populating that class with state objects. It needs to be remembered.
d
Can you log the
DownloadState
? If it gets recreated each frame, it'll be treated as an interruption to the previous animation, and transition will use spring to handle that interruption.
b
Copy code
2021-07-01 09:47:46.205 27469-27469 D/buttons: downloadState=com.gabb.pkgmgmt.data.NotInstalled@fa4991c
2021-07-01 09:47:46.205 27469-27469 D/buttons: left width=0.0.dp
2021-07-01 09:47:50.230 27469-27469 D/buttons: downloadState=com.gabb.pkgmgmt.data.Queued@869d191
2021-07-01 09:47:50.232 27469-27469 D/buttons: left width=157.71428.dp
2021-07-01 09:47:52.312 27469-27469 D/buttons: downloadState=com.gabb.pkgmgmt.data.NotInstalled@fa4991c
2021-07-01 09:47:52.313 27469-27469 D/buttons: left width=0.0.dp
d
Seems like DownloadState is not changing frequently from the logging, which is good. Is this logged above the line of
updateTransition
?
b
was being logged in buttonrow
lemme log it there
but yeah downloadState is passed from ViewModel (triggered from external conditions)
d
Also worth logging whether the transition object is being recreated, since it's used as a key for
TransitionData
here
b
@Halil Ozercan it would appear that updateTransition internally remembers
d
updateTransition
does internally remember the transition object. But if there's a
key {...}
up the tree, it'll recreate the object when the key changes.
Similarly, if the code path to invoke
updateTransition
has changed somewhere in the call stack, the updateTransition will be treated as a new Composable added to the tree. So if you have:
Copy code
if (foo) { Bar(baz1) } else Bar(baz2)
where Bar directly or indirectly calls into
updateTransition
, you'll get a brand new transition.
b
gotcha
good to know
that was it
this is an item in LazyColumn
and it was keyed w/ state
dropping the key and we flowing
thanks @Doris Liu!
d
👍
b