Search code examples
androidandroid-jetpack-composecompose-recomposition

Jetpack Compose - Recomposition ignoring function parameter


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...


Solution

  • 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?