Jetpack Compose:LazyColumn筛选数据触发IndexOutOfBoundsException求助
在Jetpack Compose的LazyColumn组件中,将经过筛选的列表传入items函数后,程序崩溃并抛出IndexOutOfBoundsException异常。应用包含两个标签页(索引为0和1),数据库中有14条type=0的条目,1条type=1的条目。
相关代码如下:
items(state.categoriesWithSubs.filter { it.categories.type == tabState }.takeIf { it.isNotEmpty() } ?: emptyList()) { categoriesWithSubs -> val filteredBalances = categoriesWithSubs.balances.filter { balances -> balances.month == 11 }.takeIf { it.isNotEmpty() } ?: emptyList() val balanceValue = if (filteredBalances.isNotEmpty()) filteredBalances.first().balance else 0.0 Card(modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), shape = RoundedCornerShape(20)) { Column(Modifier.fillMaxWidth()) { Row( Modifier .fillMaxWidth() .padding(horizontal = 16.dp) .padding(top = 32.dp), verticalAlignment = Alignment.CenterVertically ) { Image( bitmap = ImageBitmap.imageResource( StoredIcons.asRes( categoriesWithSubs.categories.icon ) ), contentDescription = "Category Image", ) Spacer(modifier = Modifier.width(8.dp)) Column(verticalArrangement = Arrangement.Center) { Text( text = categoriesWithSubs.categories.name, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold ) Text( text = getCorrectCurrencyFormat(amount = balanceValue), style = MaterialTheme.typography.titleMedium, color = if (isSystemInDarkTheme()) colorResource(id = R.color.darkonback) else colorResource( id = R.color.lightonback ), fontWeight = FontWeight.Bold ) } } Spacer(modifier = Modifier.height(16.dp)) val progressFloat = if (balanceValue > 0 && totalSumBalancesPerMonth > 0) { (balanceValue / totalSumBalancesPerMonth).toFloat() } else 0.0f LinearProgressBar( progress = progressFloat, modifier = Modifier .fillMaxWidth() .height(32.dp) .padding(horizontal = 16.dp) .clip(RoundedCornerShape(50)) ) Spacer(modifier = Modifier.height(16.dp)) HorizontalDivider( modifier = Modifier.padding(horizontal = 16.dp), thickness = 1.dp ) Spacer(modifier = Modifier.height(8.dp)) Text( text = "${(progressFloat * 100).toBigDecimal().setScale(2, RoundingMode.HALF_UP)}% der Gesamtausgaben entfallen auf diese Kategorie", modifier = Modifier.padding(horizontal = 16.dp), color = if (isSystemInDarkTheme()) colorResource(id = R.color.darkonback) else colorResource( id = R.color.lightonback ), fontWeight = FontWeight.Bold, textAlign = TextAlign.Center ) Spacer(modifier = Modifier.height(16.dp)) if(categoriesWithSubs.subcategories.isNotEmpty()) { categoriesWithSubs.subcategories.forEach { subCats -> val filteredSub = state.subcategoriesWithCategories.filter { subCats -> subCats.subcategories.catId == categoriesWithSubs.categories.id }.takeIf { it.isNotEmpty() } ?: emptyList() val filteredSubBalances = if (filteredSub.isNotEmpty()) { filteredSub[0].balancesSubs.filter { balSub -> balSub.month == 11 } } else emptyList() val filteredSubBalance = if (filteredSubBalances.isNotEmpty()) filteredSubBalances.first().balance else 0.0 Row( modifier = Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Image( bitmap = ImageBitmap.imageResource( StoredIconsSub.asRes( subCats.icon ) ), contentDescription = "SubCategory Image", modifier = Modifier .padding( start = 16.dp, end = 8.dp, top = 8.dp ) .size(24.dp) ) Text( text = "${subCats.name}: ", fontWeight = FontWeight.Bold, modifier = Modifier .fillMaxHeight() .padding(top = 8.dp) ) Text( text = getCorrectCurrencyFormat(amount = filteredSubBalance), fontWeight = FontWeight.Bold, modifier = Modifier .fillMaxHeight() .padding(top = 8.dp), color = if (isSystemInDarkTheme()) colorResource(id = R.color.darkonback) else colorResource( id = R.color.lightonback ) ) } } } Spacer(modifier = Modifier.height(32.dp)) } }
抛出的错误信息:
FATAL EXCEPTION: main
Process: com.erdemkaya.mybudgetizer, PID: 20501
java.lang.IndexOutOfBoundsException: Index 3, size 3
1. LazyColumn未指定稳定Key导致索引复用异常
你使用items函数时未为每个列表项指定唯一的稳定Key,Compose默认会用列表索引作为Key。当切换标签页时,列表长度从14条(tab0)变为1条(tab1),Compose会尝试复用之前创建的Item组件,部分组件仍会尝试访问旧列表的索引(如索引3),但新列表长度不足,直接触发IndexOutOfBoundsException。
修复方法:为items指定基于数据唯一标识的Key,比如分类ID:
items( items = state.categoriesWithSubs.filter { it.categories.type == tabState } .takeIf { it.isNotEmpty() } ?: emptyList(), key = { it.categories.id } // 使用分类的唯一ID作为稳定Key ) { categoriesWithSubs -> // 原有Item内容 }
2. 变量名冲突导致过滤逻辑错误
在子分类的forEach循环中,filter lambda的参数名subCats与外层循环的变量名重复,导致外层的subCats被覆盖,过滤条件完全不符合预期,可能引发后续列表操作的异常。
修复方法:修改filter的参数名,避免变量覆盖:
categoriesWithSubs.subcategories.forEach { subCats -> val filteredSub = state.subcategoriesWithCategories.filter { subCatItem -> // 修改参数名为subCatItem subCatItem.subcategories.catId == categoriesWithSubs.categories.id }.takeIf { it.isNotEmpty() } ?: emptyList() // 后续逻辑 }
3. 直接索引访问列表存在风险
代码中使用filteredSub[0]获取第一个元素,虽然前面判断了列表非空,但在状态快速变化时仍存在并发风险,建议使用更安全的firstOrNull()方法:
val filteredSubBalances = filteredSub.firstOrNull()?.balancesSubs?.filter { balSub -> balSub.month == 11 } ?: emptyList()
内容的提问来源于stack exchange,提问作者Erdem

