Elye
Elye

Reputation: 60081

What's the use of `MutableTransitionState`? Looks like mutableStateOf does all it can equally good

In https://developer.android.com/jetpack/compose/animation, it is mentioned that

We sometimes want to have an initial state different from the first target state. We can use updateTransition with MutableTransitionState to achieve this. For example, it allows us to start animation as soon as the code enters composition.

// Start in collapsed state and immediately animate to expanded
var currentState = remember { MutableTransitionState(BoxState.Collapsed) }
currentState.targetState = BoxState.Expanded
val transition = updateTransition(currentState)
// ...

(For full code, refer to full code example 1. below)

However, the above code is only working for initial animation but not subsequent (i.e. change from Expanded to Collapsed), as whenever the currentState.targetState changes, the composable function will run and retriggered currentState.targetState = BoxState.Expanded.

To fix the problem, I'll have to

    var currentState by remember { mutableStateOf(BoxState.Collapsed) }
    val transition = updateTransition(currentState, label = "")

    LaunchedEffect(Unit) {
        currentState.targetState = BoxState.Expanded
    }

(For full code, refer to full code example 2. below)

This will make the animation start automatically from Collapsed to Expanded. Also subsequently upon change from Expanded to Collapse (e.g. through a button click), it will still work, as the LaunchedEffect is no longer triggered.

With the LaunchedEffect, we can also do it with normal mutableStateOf, i.e. without MutableTransitionState, and still, behave well.

    var currentState by remember { mutableStateOf(BoxState.Collapsed) }
    val transition = updateTransition(currentState, label = "")

    LaunchedEffect(Unit) {
        currentState = BoxState.Expanded
    }

(For full code, refer to full code example 3. below)

So I cannot see any extra benefit using MutableTransitionState compare to the normal mutableStateOf. Anything I miss?

Full Code that describes the situation above

1. MutableTransitionState code follow document example that doesn't work

The code that doesn't works with just MutableTransitionState (i.e. no effect on button click, only having initial animation)

@Composable
fun Greeting() {
    val currentState = remember { MutableTransitionState(BoxState.Collapsed) }
    currentState.targetState = BoxState.Expanded
    val transition = updateTransition(currentState, label = "")

    val rect by transition.animateRect(label = "") { state ->
        when (state) {
            BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f)
            BoxState.Expanded -> Rect(100f, 100f, 300f, 300f)
        }
    }

    Column {
        Canvas(
            modifier = Modifier.fillMaxWidth().height(500.dp)
                .border(BorderStroke(1.dp, Color.Green))
        ) {
            drawPath(Path().apply { addRect(rect) }, Color.Red)
        }
        Button(onClick = {
            currentState.targetState =
                if (currentState.targetState == BoxState.Expanded) BoxState.Collapsed
                else BoxState.Expanded
        }) {
            Text("Click Me")
        }
    }
}

2. MutableTransitionState code with launched effect that works

The code that works with just MutableTransitionState (i.e. have effect on button click, and having initial animation)

@Composable
fun Greeting() {
    val currentState = remember { MutableTransitionState(BoxState.Collapsed) }
    val transition = updateTransition(currentState, label = "")

    val rect by transition.animateRect(label = "") { state ->
        when (state) {
            BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f)
            BoxState.Expanded -> Rect(100f, 100f, 300f, 300f)
        }
    }

    Column {
        Canvas(
            modifier = Modifier.fillMaxWidth().height(500.dp)
                .border(BorderStroke(1.dp, Color.Green))
        ) {
            drawPath(Path().apply { addRect(rect) }, Color.Red)
        }
        Button(onClick = {
            currentState.targetState =
                if (currentState.targetState == BoxState.Expanded) BoxState.Collapsed
                else BoxState.Expanded
        }) {
            Text("Click Me")
        }
    }

    LaunchedEffect(Unit) {
        currentState.targetState = BoxState.Expanded
    }
}

3. Just mutableStateOf code with launched effect that still works

The code that works with just mutableStateOf (i.e. have effect on button click, and having initial animation)

@Composable
fun Greeting() {
    var currentState by remember { mutableStateOf(BoxState.Collapsed) }
    val transition = updateTransition(currentState, label = "")

    val rect by transition.animateRect(label = "") { state ->
        when (state) {
            BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f)
            BoxState.Expanded -> Rect(100f, 100f, 300f, 300f)
        }
    }

    Column {
        Canvas(
            modifier = Modifier.fillMaxWidth().height(500.dp)
                .border(BorderStroke(1.dp, Color.Green))
        ) {
            drawPath(Path().apply { addRect(rect) }, Color.Red)
        }
        Button(onClick = {
            currentState =
                if (currentState == BoxState.Expanded) BoxState.Collapsed
                else BoxState.Expanded
        }) {
            Text("Click Me")
        }
    }

    LaunchedEffect(Unit) {
        currentState = BoxState.Expanded
    }
}

Upvotes: 2

Views: 1508

Answers (1)

Doris Liu
Doris Liu

Reputation: 987

MutableTransitionState allows you to specify a different targetState than the initial state. Note it is not a MutableState.

If you do something like this:

    val currentState = remember {
        MutableTransitionState(BoxState.Collapsed).apply {targetState = BoxState.Expanded}
    }

You can expect the Transition to start an animation going from BoxState.Collapsed to BoxState.Expanded as soon as the updateTransition is composed for the first time.

MutableTransitionState is designed to 1) trigger animations when a composable gets added to the tree (i.e. enter animations), and 2) allow observation of currentState vs targetState through the MutableTransitionState object like this: https://android.googlesource.com/platform/frameworks/support/+/e6095adbb8ffba6aede464fd06ef7302eac61860/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt#127

Upvotes: 3

Related Questions