3

I'm checking a flag value passed by a higher order component to my composable but the value returned is always the initial one...

This is my component:

@Composable
fun Test(enabled: Boolean) {
    var expanded by remember { mutableStateOf(false) }

    ExposedDropdownMenuBox(
        expanded = expanded,
        onExpandedChange = {
            if (enabled) expanded = !expanded
            println("enabled is $enabled") //always prints the initial value of enabled
        }
    ) {
        OutlinedTextField(
            value = "value",
            onValueChange = { },
            modifier = Modifier.fillMaxWidth(),
            enabled = enabled,
            readOnly = true,
            label = { Text("Label") },
            trailingIcon = {
                ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
            },
        )

        ExposedDropdownMenu(
            expanded = expanded,
            onDismissRequest = {
                expanded = false
            }
        ) {
            DropdownMenuItem(
                onClick = {
                    expanded = false
                },
                text = {
                    Text("Content")
                }
            )
        }
    }
}

The code in onExpandedChange ignores the current value of enabled.

For example, if I run the code below, I can see that the OutlinedTextField changes to enabled state after 2 seconds but onExpandedChange will use the initial (old) value of enabled.

Surface(
    color = MaterialTheme.colorScheme.background
) {
    var flag by remember { mutableStateOf(false) }
    println("flag is $flag")

    LaunchedEffect(Unit) {
        delay(2000)
        flag = !flag
        println("flag is now $flag")
    }

    Test(flag)
}

What am I doing wrong?

edit: It works fine if I do this:

@Composable
fun Test(enabled: Boolean) {
    var expanded by remember { mutableStateOf(false) }


    var enabledState by remember {
        mutableStateOf(enabled)
    }

    LaunchedEffect(enabled) {
        enabledState = enabled
    }

    ExposedDropdownMenuBox(
        expanded = expanded,
        onExpandedChange = {
            if (enabledState) expanded = !expanded
        }
    ) {
        OutlinedTextField(
            value = "value",
            onValueChange = { },
            modifier = Modifier.fillMaxWidth(),
            enabled = enabled,
            readOnly = true,
            label = { Text("Label") },
            trailingIcon = {
                ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
            },
        )

        ExposedDropdownMenu(
            expanded = expanded,
            onDismissRequest = {
                expanded = false
            }
        ) {
            DropdownMenuItem(
                onClick = {
                    expanded = false
                },
                text = {
                    Text("Content")
                }
            )
        }
    }
}

but it does not work if I do this:

@Composable
fun Test(enabled: Boolean) {
    var expanded by remember { mutableStateOf(false) }
    
    val enabledState by remember(enabled) {
        mutableStateOf(enabled)
    }

    ExposedDropdownMenuBox(
        expanded = expanded,
        onExpandedChange = {
            if (enabledState) expanded = !expanded
        }
    ) {
        OutlinedTextField(
            value = "value",
            onValueChange = { },
            modifier = Modifier.fillMaxWidth(),
            enabled = enabled,
            readOnly = true,
            label = { Text("Label") },
            trailingIcon = {
                ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
            },
        )

        ExposedDropdownMenu(
            expanded = expanded,
            onDismissRequest = {
                expanded = false
            }
        ) {
            DropdownMenuItem(
                onClick = {
                    expanded = false
                },
                text = {
                    Text("Content")
                }
            )
        }
    }
}

and I have no idea why...

z.g.y
  • 5,512
  • 4
  • 10
  • 36
  • 2
    Try rememberUpdatedState https://developer.android.com/jetpack/compose/side-effects#rememberupdatedstate – Bruno Sep 05 '22 at 11:01

2 Answers2

2

Recomposition is not ignoring updated function parameter but closure of onExpandedChange is. It retains previous instance of MutableState even if you re-instantiate it by changing remember key. So what you should be doing is updating value of current instance of MutableState instead of

   val enabledState = remember(enabled) {
        mutableStateOf(enabled)
    }

you should either use rememberUpdatedState as Bruno suggested or update value with

val enabledState by remember {
    mutableStateOf(enabled)
}.apply {
    value = enabled
}

which is what rememberUpdatedState implementation is

Difference between remember and rememberUpdatedState in Jetpack Compose?

Thracian
  • 43,021
  • 16
  • 133
  • 222
0

use rememberUpdatedState , like

val currentEnabledState by rememberUpdatedState(enabled)

and then pass the currentEnabledState in different scope.

thelearner
  • 46
  • 3