3
votes

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 {...}?

1
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

1 Answers

2
votes

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
        },
    )
}