android jetpack compose - When does recomposition occur? - Stack Overflow

I’m using an IconToggleButton to create a single-choice list, and the variable that tracks whether an i

I’m using an IconToggleButton to create a single-choice list, and the variable that tracks whether an item is selected is var choiceChecked: Pair<Int, MutableState>? = null. This variable stores the index of the selected item and its selected state.

When I click a button outside the list and print choiceChecked.first, it prints the correct value. It seems that even though choiceChecked doesn't use remember and MutableState, it can still retain its value after recomposition.

However, when I turn off the screen and turn it back on, the value of choiceChecked is lost. I speculate that the reason is that after selecting an item, choiceChecked recorded the value during that recomposition. Later, when I click the button to print choiceChecked.first, no recomposition occurs because the state hasn't changed, so it prints the correct value.

But when I turn off the screen and turn it back on, recomposition happens, and choiceChecked is lost. Is this correct?

Below is my code,Please five me for posting such long code, as I am unsure if other variables might affect recomposition. The code block that prints choiceChecked is commented with 'print the choiceChecked'. The code for selecting the item is inside the clickable. Please search for clickable.

    @Composable
    fun AlbumGrouping(
        showPopup: MutableState<Boolean>,
        isPopupVisible: MutableState<Boolean>,
        size: DpSize,
        application: MediaApplication,
        directoryIcon: Bitmap,
    ) {
        val width = remember { (size.width.value * 0.9f).dp }
        val height = remember { (size.height.value * 0.6f).dp }
        val density = LocalDensity.current
    
        //加载数据
        val scope = rememberCoroutineScope()
        val items: SnapshotStateList<Album> = remember { mutableStateListOf() }
        var queryJob by remember { mutableStateOf<Job?>(null) }
        var addJob by remember { mutableStateOf<Job?>(null) }
        val gridState = rememberLazyGridState()
    
        //控制显示
        val transition = updateTransition(targetState = showPopup.value, label = "popup")
        val alpha by transition.animateFloat(
            label = "popupOffset",
            transitionSpec = { tween(durationMillis = 500) }
        ) { state -> if (state) 1f else 0f }
    
        // 监听 showPopup 的变化,管理 Popup 显示状态
        LaunchedEffect(showPopup.value) {
            queryJob?.cancelAndJoin()
            if (showPopup.value) {
                queryJob = scope.launch(Dispatchers.IO) {
                    val result = application.mediaDatabase.albumDao.queryByParentId(-1)
                    if (result != null) items.addAll(result)
                }
                isPopupVisible.value = true
            } else {
                delay(500)
                isPopupVisible.value = false
                queryJob?.cancel()
            }
        }
    
        var choiceChecked: Pair<Int, MutableState<Boolean>>? = null
        if (isPopupVisible.value) {
            Popup(
                alignment = Alignment.TopCenter,
                onDismissRequest = {
                    showPopup.value = false
                },
                properties = PopupProperties(focusable = true)
            ) {
                Column(
                    modifier = Modifier
                        .size(width, height)
                        .graphicsLayer(alpha = alpha)
                        .background(Color(0xE6FFFFFF), shape = RoundedCornerShape(16.dp))
                ) {
                    LazyVerticalGrid(
                        state = gridState,
                        columns = GridCells.Fixed(3),
                        modifier = Modifier
                            .weight(1f)
                            .fillMaxWidth()
                            .padding(16.dp)
                    ) {
                        items(items.size) { index ->
                            items[index].let { item ->
                                val checked = remember { mutableStateOf(false) }
                                Box(
                                    contentAlignment = Alignment.TopEnd,
                                    modifier = Modifier
                                        .padding(end = TinyPadding, top = TinyPadding)
                                        .clickable {
                                            if (checked.value) {
                                                checked.value = false
                                            } else {
                                                choiceChecked?.second?.value = false
                                                choiceChecked = index to checked
                                                checked.value = true
                                            }
                                        }
                                ) {
                                    Column {
                                        DisplayImage(
                                            bitmap = directoryIcon,
                                            context = application.baseContext,
                                            modifier = Modifier.aspectRatio(1f)
                                        )
                                        Text(
                                            text = item.name,
                                            style = MaterialTheme.typography.labelMedium,
                                            modifier = Modifier.padding(start = SmallPadding)
                                        )
                                    }
    
                                    IconToggleButton(
                                        checked = checked.value,
                                        colors = IconButtonDefaults.iconToggleButtonColors(
                                            containerColor = Color(0x80808080), // 选中时图标的颜色
                                            contentColor = Color.White,              // 未选中时图标的颜色
                                            checkedContainerColor = Color.White,    // 选中时背景颜色
                                            checkedContentColor = Color.DarkGray   // 未选中时背景颜色
                                        ),
                                        onCheckedChange = {
                                            if (checked.value) {
                                                checked.value = false
                                            } else {
                                                choiceChecked?.second?.value = false
                                                choiceChecked = index to checked
                                                checked.value = true
                                            }
                                        },
                                        modifier = Modifier
                                            .width(24.dp)
                                            .height(24.dp)
                                            .offset(x = (-10).dp, y = 10.dp)
                                    ) {
                                        if (checked.value) {
                                            Icon(
                                                Icons.Filled.CheckCircle,
                                                contentDescription = "选中",
                                                modifier = Modifier
                                                    .fillMaxSize()
                                                    .padding(0.dp)
                                            )
                                        } else {
                                            Icon(
                                                Icons.Filled.RadioButtonUnchecked,
                                                contentDescription = "未选中",
                                                modifier = Modifier
                                                    .fillMaxSize()
                                                    .padding(0.dp)
                                            )
                                        }
                                    }
                                }
                            }
                        }
                    }
                    val driver = remember { mutableStateOf(0.dp) }
                    Row(
                        horizontalArrangement = Arrangement.SpaceAround,
                        verticalAlignment = Alignment.CenterVertically,
                        modifier = Modifier
                            .wrapContentHeight()
                            .fillMaxWidth()
                            .onGloballyPositioned { layout ->
                                with(density) {
                                    driver.value = layout.size.height.toDp()
                                }
                            }
                            .background(Color(0xE6D3D3D3), shape = RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp))
                    ) {
                        TextButton(onClick = { //print the choiceChecked
                            println("index${choiceChecked?.first}")
                        }) {
                            Text(stringResource(R.string.popup_select_grouping))
                        }
                        Text(
                            "", modifier = Modifier
                                .width(1.dp)
                                .height(driver.value)
                                .background(Color(0xE6FFFFFF))
                        )
                        TextButton(onClick = {
                            scope.launch(Dispatchers.IO) {
                                addJob?.join()
                                addJob = scope.launch {
                                    val groupingNum = items.size
                                    val album = Album(name = "分组${groupingNum + 1}")
                                    application.mediaDatabase.albumDao.insert(album)
                                    items.add(album)
                                }
                            }
                        }) {
                            Text(stringResource(R.string.popup_add_grouping))
                        }
                    }
                }
            }
        }
    }

I’m using an IconToggleButton to create a single-choice list, and the variable that tracks whether an item is selected is var choiceChecked: Pair<Int, MutableState>? = null. This variable stores the index of the selected item and its selected state.

When I click a button outside the list and print choiceChecked.first, it prints the correct value. It seems that even though choiceChecked doesn't use remember and MutableState, it can still retain its value after recomposition.

However, when I turn off the screen and turn it back on, the value of choiceChecked is lost. I speculate that the reason is that after selecting an item, choiceChecked recorded the value during that recomposition. Later, when I click the button to print choiceChecked.first, no recomposition occurs because the state hasn't changed, so it prints the correct value.

But when I turn off the screen and turn it back on, recomposition happens, and choiceChecked is lost. Is this correct?

Below is my code,Please five me for posting such long code, as I am unsure if other variables might affect recomposition. The code block that prints choiceChecked is commented with 'print the choiceChecked'. The code for selecting the item is inside the clickable. Please search for clickable.

    @Composable
    fun AlbumGrouping(
        showPopup: MutableState<Boolean>,
        isPopupVisible: MutableState<Boolean>,
        size: DpSize,
        application: MediaApplication,
        directoryIcon: Bitmap,
    ) {
        val width = remember { (size.width.value * 0.9f).dp }
        val height = remember { (size.height.value * 0.6f).dp }
        val density = LocalDensity.current
    
        //加载数据
        val scope = rememberCoroutineScope()
        val items: SnapshotStateList<Album> = remember { mutableStateListOf() }
        var queryJob by remember { mutableStateOf<Job?>(null) }
        var addJob by remember { mutableStateOf<Job?>(null) }
        val gridState = rememberLazyGridState()
    
        //控制显示
        val transition = updateTransition(targetState = showPopup.value, label = "popup")
        val alpha by transition.animateFloat(
            label = "popupOffset",
            transitionSpec = { tween(durationMillis = 500) }
        ) { state -> if (state) 1f else 0f }
    
        // 监听 showPopup 的变化,管理 Popup 显示状态
        LaunchedEffect(showPopup.value) {
            queryJob?.cancelAndJoin()
            if (showPopup.value) {
                queryJob = scope.launch(Dispatchers.IO) {
                    val result = application.mediaDatabase.albumDao.queryByParentId(-1)
                    if (result != null) items.addAll(result)
                }
                isPopupVisible.value = true
            } else {
                delay(500)
                isPopupVisible.value = false
                queryJob?.cancel()
            }
        }
    
        var choiceChecked: Pair<Int, MutableState<Boolean>>? = null
        if (isPopupVisible.value) {
            Popup(
                alignment = Alignment.TopCenter,
                onDismissRequest = {
                    showPopup.value = false
                },
                properties = PopupProperties(focusable = true)
            ) {
                Column(
                    modifier = Modifier
                        .size(width, height)
                        .graphicsLayer(alpha = alpha)
                        .background(Color(0xE6FFFFFF), shape = RoundedCornerShape(16.dp))
                ) {
                    LazyVerticalGrid(
                        state = gridState,
                        columns = GridCells.Fixed(3),
                        modifier = Modifier
                            .weight(1f)
                            .fillMaxWidth()
                            .padding(16.dp)
                    ) {
                        items(items.size) { index ->
                            items[index].let { item ->
                                val checked = remember { mutableStateOf(false) }
                                Box(
                                    contentAlignment = Alignment.TopEnd,
                                    modifier = Modifier
                                        .padding(end = TinyPadding, top = TinyPadding)
                                        .clickable {
                                            if (checked.value) {
                                                checked.value = false
                                            } else {
                                                choiceChecked?.second?.value = false
                                                choiceChecked = index to checked
                                                checked.value = true
                                            }
                                        }
                                ) {
                                    Column {
                                        DisplayImage(
                                            bitmap = directoryIcon,
                                            context = application.baseContext,
                                            modifier = Modifier.aspectRatio(1f)
                                        )
                                        Text(
                                            text = item.name,
                                            style = MaterialTheme.typography.labelMedium,
                                            modifier = Modifier.padding(start = SmallPadding)
                                        )
                                    }
    
                                    IconToggleButton(
                                        checked = checked.value,
                                        colors = IconButtonDefaults.iconToggleButtonColors(
                                            containerColor = Color(0x80808080), // 选中时图标的颜色
                                            contentColor = Color.White,              // 未选中时图标的颜色
                                            checkedContainerColor = Color.White,    // 选中时背景颜色
                                            checkedContentColor = Color.DarkGray   // 未选中时背景颜色
                                        ),
                                        onCheckedChange = {
                                            if (checked.value) {
                                                checked.value = false
                                            } else {
                                                choiceChecked?.second?.value = false
                                                choiceChecked = index to checked
                                                checked.value = true
                                            }
                                        },
                                        modifier = Modifier
                                            .width(24.dp)
                                            .height(24.dp)
                                            .offset(x = (-10).dp, y = 10.dp)
                                    ) {
                                        if (checked.value) {
                                            Icon(
                                                Icons.Filled.CheckCircle,
                                                contentDescription = "选中",
                                                modifier = Modifier
                                                    .fillMaxSize()
                                                    .padding(0.dp)
                                            )
                                        } else {
                                            Icon(
                                                Icons.Filled.RadioButtonUnchecked,
                                                contentDescription = "未选中",
                                                modifier = Modifier
                                                    .fillMaxSize()
                                                    .padding(0.dp)
                                            )
                                        }
                                    }
                                }
                            }
                        }
                    }
                    val driver = remember { mutableStateOf(0.dp) }
                    Row(
                        horizontalArrangement = Arrangement.SpaceAround,
                        verticalAlignment = Alignment.CenterVertically,
                        modifier = Modifier
                            .wrapContentHeight()
                            .fillMaxWidth()
                            .onGloballyPositioned { layout ->
                                with(density) {
                                    driver.value = layout.size.height.toDp()
                                }
                            }
                            .background(Color(0xE6D3D3D3), shape = RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp))
                    ) {
                        TextButton(onClick = { //print the choiceChecked
                            println("index${choiceChecked?.first}")
                        }) {
                            Text(stringResource(R.string.popup_select_grouping))
                        }
                        Text(
                            "", modifier = Modifier
                                .width(1.dp)
                                .height(driver.value)
                                .background(Color(0xE6FFFFFF))
                        )
                        TextButton(onClick = {
                            scope.launch(Dispatchers.IO) {
                                addJob?.join()
                                addJob = scope.launch {
                                    val groupingNum = items.size
                                    val album = Album(name = "分组${groupingNum + 1}")
                                    application.mediaDatabase.albumDao.insert(album)
                                    items.add(album)
                                }
                            }
                        }) {
                            Text(stringResource(R.string.popup_add_grouping))
                        }
                    }
                }
            }
        }
    }
Share Improve this question edited Feb 3 at 0:32 QAQ asked Feb 3 at 0:18 QAQQAQ 491 silver badge4 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 2

I made a smaller sample to support my answer:

@Composable
fun RecompositionDemo() {
    val context = LocalContext.current
    var choiceChecked = 1
    Column {
        OutlinedButton(
            onClick = {
                choiceChecked++
            }
        ) {
            Text("INCREASE")
        }
        OutlinedButton(
            onClick = {
                Toast.makeText(context, "choiceChecked = $choiceChecked", Toast.LENGTH_SHORT).show()
            }
        ) {
            Text("PRINT")
        }
        Text("choiceChecked = $choiceChecked")
    }
}

While execution, pay close attention to a) the choiceChecked value shown by the Toast and b) the choiceChecked value shown by the Text Composable:

What we can observe is

  • the Toast initially displays choiceChecked = 1
  • then we click the INCREASE OutlinedButton
  • the Text does not update to show the new value
  • the Toast however will correctly show choiceChecked = 2
  • after locking and unlocking the device, a recomposition happens which resets choiceChecked = 1

But when I turn off the screen and turn it back on, recomposition happens, and choiceChecked is lost. Is this correct?

This is correct.

When I click a button outside the list and print choiceChecked.first, it prints the correct value. It seems that even though choiceChecked doesn't use remember and MutableState, it can still retain its value after recomposition.

That is not correct. The reason while the Toast prints the correct choiceChecked values is because there is no recomposition triggered in the first place when doing choiceChecked++, and thus the value is not reset. At the same time, as no recomposition is happening, the Text Composable is not updating.

Jetpack Compose can only detect changes on MutableState variables, that's why we use mutableStateOf to make sure the UI gets recomposed when a variable value changes. Additionally, we use remember so that any new value set to a variable survives the recomposition.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745253405a4618811.html

相关推荐

  • android jetpack compose - When does recomposition occur? - Stack Overflow

    I’m using an IconToggleButton to create a single-choice list, and the variable that tracks whether an i

    2小时前
    20

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信