首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >ConcurrentModificationException在mutableStateListOf反转过程中的应用

ConcurrentModificationException在mutableStateListOf反转过程中的应用
EN

Stack Overflow用户
提问于 2022-02-15 05:31:26
回答 1查看 458关注 0票数 1
代码语言:javascript
运行
复制
itemList.reverse()

itemListviewModel中的mutableStateListOf()对象,上面的行抛出在给定异常下面:

代码语言:javascript
运行
复制
java.util.ConcurrentModificationException
    at androidx.compose.runtime.snapshots.StateListIterator.validateModification(SnapshotStateList.kt:278)
    at androidx.compose.runtime.snapshots.StateListIterator.set(SnapshotStateList.kt:271)
    at java.util.Collections.reverse(Collections.java:435)
    at kotlin.collections.CollectionsKt___CollectionsJvmKt.reverse(_CollectionsJvm.kt:43)
    at in.rachika.composetest2.Tests.LazyColumnHeaderTest$MainScreenWithChildList$1$2.invoke(LazyColumnHeaderTest.kt:114)
    at in.rachika.composetest2.Tests.LazyColumnHeaderTest$MainScreenWithChildList$1$2.invoke(LazyColumnHeaderTest.kt:111)

我无法弄清楚如何逆转或洗牌mutableStateListOf()对象

reverse()在孤立的情况下工作得很好,但是我的LazyColumnstickyHeader()SwipeToDismiss(),不知道这可能会造成问题。

模型

代码语言:javascript
运行
复制
data class TestModel(
val isHeader: Boolean,
val UniqueKey: UUID,
val GroupId: UUID,
val GroupName: String,
val ItemName: String,
val children: MutableList<TestModel>,
var isExpanded: Boolean = false)

ViewModel

代码语言:javascript
运行
复制
class TestViewModel: ViewModel() {
var itemList = mutableStateListOf<TestModel>()

init {
    viewModelScope.launch(Dispatchers.IO){
        loadList()
    }
}

private fun loadList() {
    for(i in 0..20){
        val groupName = "${i + 1}. STICKY HEADER #"
        val groupUUID = UUID.randomUUID()

        val childList = mutableListOf<TestModel>()
        for(t in 0..Random.nextInt(10, 20)){
            childList.add(TestModel(
                isHeader = false,
                UniqueKey = UUID.randomUUID(),
                GroupId = groupUUID,
                GroupName = groupName,
                ItemName = "${t + 1}. This is an CHILD ITEM... #${i + 1} - ${Random.nextInt(1001, 5001)}",
                children = ArrayList()
            )
            )
        }

        viewModelScope.launch(Dispatchers.Main){
            itemList.add(TestModel(
                isHeader = true,
                UniqueKey = UUID.randomUUID(),
                GroupId = groupUUID,
                GroupName = groupName,
                ItemName = "",
                children = childList
            ))
        }
    }
}

fun addChildren(testModel: TestModel, onCompleted: (startIndex: Int) -> Unit){
    if(testModel.children.count() > 0){
        var index = itemList.indexOf(testModel)
        testModel.children.forEach { tItem ->
            itemList.add(index + 1, tItem)
            index++
        }
        testModel.apply {
            isExpanded = true
            children.clear()
        }
        onCompleted(index)
    }
}

fun removeChildren(testModel: TestModel, onCompleted: (startIndex: Int) -> Unit){
    val startIndex = itemList.indexOf(testModel) + 1
    while (startIndex < itemList.size && !itemList[startIndex].isHeader){
        testModel.children.add(itemList.removeAt(startIndex))
    }
    if(testModel.children.count() > 0){
        testModel.isExpanded = false
        onCompleted(startIndex - 1)
    }
}}

可组合函数

代码语言:javascript
运行
复制
@Composable
fun MainScreenWithChildList(testViewModel: TestViewModel = viewModel()) {
    val lazyColumnState = rememberLazyListState()
    var reverseList by remember { mutableStateOf(false) }
    if (reverseList) {
        LaunchedEffect(Unit) {
            delay(1000)
            testViewModel.itemList.reverse()
        }
    }

    Box(modifier = Modifier.fillMaxSize()) {
        LazyColumn(
            state = lazyColumnState,
            modifier = Modifier.fillMaxSize()
        ) {
            testViewModel.itemList.forEach { testModel ->
                when (testModel.isHeader) {
                    true -> {
                        stickyHeader(key = testModel.UniqueKey) {
                            HeaderLayout(testModel = testModel) { testModel ->
                                if (testModel.isExpanded) {
                                    testViewModel.removeChildren(testModel) {}
                                } else {
                                    testViewModel.addChildren(testModel) {}
                                }
                            }
                        }
                    }
                    false -> {
                        item(key = testModel.UniqueKey) {
                            val dismissState = rememberDismissState()
                            if (dismissState.isDismissed(DismissDirection.EndToStart) || dismissState.isDismissed(
                                    DismissDirection.StartToEnd
                                )
                            ) {
                                if (dismissState.currentValue != DismissValue.Default) {
                                    LaunchedEffect(Unit) {
                                        dismissState.reset()
                                    }
                                }
                            }

                            SwipeToDismiss(
                                state = dismissState,
                                directions = setOf(
                                    DismissDirection.StartToEnd,
                                    DismissDirection.EndToStart
                                ),
                                dismissThresholds = { direction ->
                                    FractionalThreshold(if (direction == DismissDirection.StartToEnd || direction == DismissDirection.EndToStart) 0.25f else 0.5f)
                                },
                                background = { SwipedItemBackground(dismissState = dismissState) },
                                dismissContent = {
                                    ItemScreen(
                                        modifier = Modifier
                                            .fillMaxWidth()
                                            .animateItemPlacement(animationSpec = tween(600)),
                                        elevation = if (dismissState.dismissDirection != null) 16 else 0,
                                        testModel = testModel
                                    )
                                }
                            )
                        }
                    }
                }
            }
        }

        Button(
            onClick = {
                reverseList = true
            },
            modifier = Modifier.align(Alignment.BottomCenter)
        ) {
            Text(text = "Reverse")
        }
    }
}

@Composable
fun ItemScreen(modifier: Modifier, elevation: Int, testModel: TestModel) {
    Card(
        modifier = modifier,
        //elevation = animateDpAsState(elevation.dp).value
    ) {
        Text(
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 16.dp, vertical = 8.dp),
            text = testModel.ItemName + "  ="
        )
    }
}

@Composable
fun SwipedItemBackground(dismissState: DismissState) {
    val direction = dismissState.dismissDirection ?: return
    val color by animateColorAsState(
        targetValue = when (dismissState.targetValue) {
            DismissValue.Default -> Color.LightGray
            DismissValue.DismissedToEnd -> Color.Green
            DismissValue.DismissedToStart -> Color.Red
        }
    )
    val icon = when (direction) {
        DismissDirection.StartToEnd -> Icons.Default.Done
        DismissDirection.EndToStart -> Icons.Default.Delete
    }
    val scale by animateFloatAsState(
        if (dismissState.targetValue == DismissValue.Default) 0.75f else 1.5f
    )
    val alignment = when (direction) {
        DismissDirection.StartToEnd -> Alignment.CenterStart
        DismissDirection.EndToStart -> Alignment.CenterEnd
    }
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(color)
            .padding(start = 12.dp, end = 12.dp),
        contentAlignment = alignment
    ) {
        Icon(icon, contentDescription = "Icon", modifier = Modifier.scale(scale))
    }
}

@Composable
fun HeaderLayout(testModel: TestModel, onExpandClicked: (testModel: TestModel) -> Unit) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .background(
                color = Color.LightGray,
                shape = RoundedCornerShape(8.dp)
            )
            .padding(horizontal = 32.dp, vertical = 16.dp),
        horizontalArrangement = SpaceBetween

    ) {
        Text(text = testModel.GroupName)
        TextButton(onClick = { onExpandClicked(testModel) }) {
            Text(text = if (testModel.isExpanded) "Collapse" else "Expand")
        }
    }
}

上面的是完全可复制的代码。请复制粘贴并尝试

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-02-15 06:39:29

当列表大小很小时,reverse似乎工作得很好,但是当项目数量很大时,它就会崩溃。我能够用下面的MRE复制这个

代码语言:javascript
运行
复制
val list = remember { (1..100).toList().toMutableStateList() }
LaunchedEffect(Unit) {
    delay(1.seconds)
    list.reverse()
}
Text(list.toList().toString())

已报告这个组成问题跟踪器,星光它可以更快地解决。

在此之前,您可以手动反转它,如下所示:

代码语言:javascript
运行
复制
val newItems = list.reversed()
list.clear()
list.addAll(newItems)
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71121504

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档