5

I have this composable function that a button will toggle show the text and hide it

@Composable
fun Greeting() {
    Column {
        val toggleState = remember {
            mutableStateOf(false)
        }
        AnimatedVisibility(visible = toggleState.value) {
            Text(text = "Edit", fontSize = 64.sp)
        }
        ToggleButton(toggleState = toggleState) {}
    }
}

@Composable
fun ToggleButton(modifier: Modifier = Modifier,
                 toggleState: MutableState<Boolean>,
                 onToggle: (Boolean) -> Unit) {


    TextButton(
        modifier = modifier,
        onClick = {
            toggleState.value = !toggleState.value
            onToggle(toggleState.value)
        })
    { Text(text = if (toggleState.value) "Stop" else "Start") }
}

One thing I didn't like the code is val toggleState = remember { ... }.
I prefer val toggleState by remember {...}

However, if I do that, as shown below, I cannot pass the toggleState over to ToggleButton, as ToggleButton wanted mutableState<Boolean> and not Boolean. Hence it will error out.


@Composable
fun Greeting() {
    Column {
        val toggleState by remember {
            mutableStateOf(false)
        }
        AnimatedVisibility(visible = toggleState) {
            Text(text = "Edit", fontSize = 64.sp)
        }
        ToggleButton(toggleState = toggleState) {} // Here will have error
    }
}

@Composable
fun ToggleButton(modifier: Modifier = Modifier,
                 toggleState: MutableState<Boolean>,
                 onToggle: (Boolean) -> Unit) {


    TextButton(
        modifier = modifier,
        onClick = {
            toggleState.value = !toggleState.value
            onToggle(toggleState.value)
        })
    { Text(text = if (toggleState.value) "Stop" else "Start") }
}

How can I fix the above error while still using val toggleState by remember {...}?

Elye
  • 53,639
  • 54
  • 212
  • 474
  • Note that I think that the recommended pattern is to pass a `State`, not a `MutableState`, and to have the function type (`onToggle`) implementation be responsible for updating the `MutableState`. That does not change the nature of your problem, insofar as you do not have direct reference to either a `State` or a `MutableState`, given your use of the property delegate. – CommonsWare Mar 30 '21 at 12:06

1 Answers1

4

State hoisting in Compose is a pattern of moving state to a composable's caller to make a composable stateless. The general pattern for state hoisting in Jetpack Compose is to replace the state variable with two parameters:

  • value: T: the current value to display
  • onValueChange: (T) -> Unit: an event that requests the value to change, where T is the proposed new value

You can do something like

// stateless composable is responsible 
@Composable
fun ToggleButton(modifier: Modifier = Modifier,
                 toggle: Boolean,
                 onToggleChange: () -> Unit) {
    
    TextButton(
        onClick = onToggleChange,
        modifier = modifier
    )
    { Text(text = if (toggle) "Stop" else "Start") }

}

and

@Composable
fun Greeting() {

        var toggleState by remember { mutableStateOf(false) }

        AnimatedVisibility(visible = toggleState) {
            Text(text = "Edit", fontSize = 64.sp)
        }
        ToggleButton(toggle = toggleState,
            onToggleChange = { toggleState = !toggleState }
        )
}

You can also add the same stateful composable which is only responsible for holding internal state:

@Composable
fun ToggleButton(modifier: Modifier = Modifier) {

    var toggleState by remember { mutableStateOf(false) }

    ToggleButton(modifier,
        toggleState,
        onToggleChange = {
            toggleState = !toggleState
        },
    )
}
Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
  • Nice. Either passing the state or have a separate state in the ToggleButton. I think the first one (stateless) is better. – Elye Mar 30 '21 at 22:30