You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 02:47:33