Elye
Elye

Reputation: 60351

How to use by delegate for mutableState yet make it passable to another function?

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

Upvotes: 7

Views: 2324

Answers (1)

Gabriele Mariotti
Gabriele Mariotti

Reputation: 365138

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

Upvotes: 4

Related Questions