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

Compose LazyColumn列表项点击触发应用崩溃问题排查

问题:Jetpack Compose LazyColumn点击列表项崩溃(IndexOutOfBoundsException)

使用LazyColumn展示列表时,点击列表项会触发IndexOutOfBoundsException导致应用崩溃;将LazyColumn替换为普通Column后无崩溃问题。


相关代码

ViewModel代码

package com.example.workspace.viewmodels

import androidx.lifecycle.ViewModel
import com.example.workspace.ui.screens.FunnelListItem
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class FunnelScreenViewModel @Inject constructor(

) : ViewModel() {
    val ITEM_AREA = 0
    val ITEM_HQ = 1
    val ITEM_PATCH = 2
    val ITEM_CUSTOMER = 3

    val screenTitles = listOf(
        "Select Area",
        "Select HQ",
        "Select Patch",
        "Select Customer"
    )

    val startingPoint = ITEM_AREA

    fun getAreas() = mutableListOf(
        FunnelListItem(ITEM_AREA, 1, "Coimbatore BM"),
        FunnelListItem(ITEM_AREA, 2, "Madurai BM"),
        FunnelListItem(ITEM_AREA, 3, "Trichy BM"),
        )

    fun getHQs() = mutableListOf(
        FunnelListItem(ITEM_HQ, 1, "Madurai 01"),
        FunnelListItem(ITEM_HQ, 2, "Madurai 02"),
        )

    fun getPatches() = mutableListOf(
        FunnelListItem(ITEM_PATCH, 1, "Madurai"),
        FunnelListItem(ITEM_PATCH, 2, "Theni"),
        )

    fun getCustomers() = mutableListOf(
        FunnelListItem(ITEM_CUSTOMER, 1, "Saravanan"),
        FunnelListItem(ITEM_CUSTOMER, 2, "Pandian"),
        FunnelListItem(ITEM_CUSTOMER, 3, "Anbu"),
        FunnelListItem(ITEM_CUSTOMER, 4, "Anbu"),
        FunnelListItem(ITEM_CUSTOMER, 5, "Anbu")
    )  
}

Screen可组合项代码

package com.example.workspace.ui.screens

import androidx.activity.compose.BackHandler
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
import com.example.workspace.ui.components.SimpleTopAppBar
import com.example.workspace.ui.navigation.Screen
import com.example.workspace.viewmodels.FunnelScreenViewModel


@Composable
fun FunnelScreen(
    navHostController: NavHostController,
    funnelScreenViewModel: FunnelScreenViewModel = hiltViewModel()
){
    val startingPoint = funnelScreenViewModel.startingPoint
    var currentItemType by rememberSaveable {
        mutableIntStateOf(funnelScreenViewModel.startingPoint)
    }

    val listItems = when(currentItemType) {
        funnelScreenViewModel.ITEM_AREA -> funnelScreenViewModel.getAreas()
        funnelScreenViewModel.ITEM_HQ -> funnelScreenViewModel.getHQs()
        funnelScreenViewModel.ITEM_PATCH -> funnelScreenViewModel.getPatches()
        else -> funnelScreenViewModel.getCustomers()
    }

    fun handleBack() {
        if (currentItemType == 0) {
            navHostController.popBackStack()
        } else {
            currentItemType--
        }
    }
    BackHandler {
        handleBack()
    }

    Scaffold(
        topBar = {
            SimpleTopAppBar(title = funnelScreenViewModel.screenTitles[currentItemType]) {
               handleBack()

            }
        }
    ) {
        paddingValues -> FunnelScreenContent(Modifier.padding(paddingValues), listItems, onItemClicked = {
           if (currentItemType < 3) {
               currentItemType++
           } else {
               navHostController.navigate(Screen.CallDetailsScreen.route)
           }
    })
    }

}

@Composable
fun FunnelScreenContent(
    modifier: Modifier,
    listItems: List<FunnelListItem>,
    onItemClicked: () -> Unit
) {
    LazyColumn(modifier.fillMaxWidth()){
        items(listItems) {
            listItem -> Text(
            text = listItem.name,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .clickable {
                        onItemClicked()
                    }
            )
        }
    }
}

data class FunnelListItem(
    val itemType: Int,
    val id: Int,
    val name: String
)

崩溃堆栈信息

FATAL EXCEPTION: main
Process: com.seluriapharma.workspace, PID: 31984
java.lang.IndexOutOfBoundsException: Index 2, size 2
at
androidx.compose.foundation.lazy.layout.MutableIntervalList.checkIndexBounds(IntervalList.kt:183)
at
androidx.compose.foundation.lazy.layout.MutableIntervalList.get(IntervalList.kt:166)
at
androidx.compose.foundation.lazy.layout.LazyLayoutIntervalContent.getKey(LazyLayoutIntervalContent.kt:86)
at
androidx.compose.foundation.lazy.LazyListItemProviderImpl.getKey(LazyListItemProvider.kt:85)
at
androidx.compose.foundation.lazy.LazyListMeasuredItemProvider.getAndMeasure(LazyListMeasuredItemProvider.kt:46)
at
androidx.compose.foundation.lazy.LazyListMeasureKt.createItemsAfterList(LazyListMeasure.kt:418)
at
androidx.compose.foundation.lazy.LazyListMeasureKt.measureLazyList-50ZNEM8(LazyListMeasure.kt:278)

替换为Column后的正常代码片段

@Composable
fun FunnelScreenContent(
    modifier: Modifier,
    currentItemType: Int,
    funnelScreenViewModel: FunnelScreenViewModel,
    onItemClicked: () -> Unit
) {
    val listItems = when(currentItemType) {
        funnelScreenViewModel.ITEM_AREA -> funnelScreenViewModel.getAreas()
        funnelScreenViewModel.ITEM_HQ -> funnelScreenViewModel.getHQs()
        funnelScreenViewModel.ITEM_PATCH -> funnelScreenViewModel.getPatches()
        else -> funnelScreenViewModel.getCustomers()
    }

    Column(modifier.fillMaxWidth()) {
        listItems.forEach {
           item -> Text(
                text = item.name,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .clickable {
                        onItemClicked()
                    }
            )
        }
    }
}

依赖配置

dependencies {

    implementation("androidx.core:core-ktx:1.12.0")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
    implementation("androidx.activity:activity-compose:1.8.0")
    implementation(platform("androidx.compose:compose-bom:2023.03.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.ui:ui-graphics")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.compose.material3:material3:1.2.0-alpha10")
    implementation("androidx.navigation:navigation-compose:2.7.4")

    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")

    val ktorVersion = "1.6.3"
    implementation("io.ktor:ktor-client-core:$ktorVersion")
    implementation("io.ktor:ktor-client-android:$ktorVersion")
    implementation("io.ktor:ktor-client-serialization:$ktorVersion")
    implementation("io.ktor:ktor-client-logging:$ktorVersion")
    implementation("ch.qos.logback:logback-classic:1.2.3")

    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0")

    // ObjectBox dependencies
    implementation("io.objectbox:objectbox-android:3.7.0")

    // Dagger hilt
    implementation("com.google.dagger:hilt-android:2.48.1")
    kapt("com.google.dagger:hilt-android-compiler:2.44")
    implementation("androidx.hilt:hilt-navigation-compose:1.0.0")

    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
    androidTestImplementation("androidx.compose.ui:ui-test-junit4")
    debugImplementation("androidx.compose.ui:ui-tooling")
    debugImplementation("androidx.compose.ui:ui-test-manifest")
}

原因分析

  • LazyColumn的items方法默认使用元素的位置索引作为复用key,当点击列表项更新currentItemType后,listItems被替换为新列表(比如从3个元素的Area列表切换到2个元素的HQ列表),LazyColumn会尝试复用之前已渲染的item组件,但新列表的元素数量少于旧列表,导致访问超出新列表长度的索引,触发IndexOutOfBoundsException。
  • 普通Column没有组件复用逻辑,每次状态更新都会重新渲染所有元素,因此不会出现索引越界问题。
  • 另外,ViewModel中每次调用getAreas()等方法都会返回新的MutableList实例,且未提供稳定唯一的标识key,进一步加剧了LazyColumn的复用冲突。

修复方案

1. 为LazyColumn的items指定稳定唯一的key

修改FunnelScreenContent中的LazyColumn代码,使用FunnelListItem的id作为唯一key,让LazyColumn能正确识别元素身份,避免错误复用:

@Composable
fun FunnelScreenContent(
    modifier: Modifier,
    listItems: List<FunnelListItem>,
    onItemClicked: () -> Unit
) {
    LazyColumn(modifier.fillMaxWidth()){
        // 指定key为item的id,确保元素身份唯一
        items(listItems, key = { it.id }) { listItem ->
            Text(
                text = listItem.name,
                modifier = Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .clickable {
                        onItemClicked()
                    }
            )
        }
    }
}

2. 优化ViewModel的列表提供方式(可选但推荐)

将ViewModel中的可变列表改为不可变列表并缓存,避免每次调用都创建新实例,提升性能同时增强稳定性:

@HiltViewModel
class FunnelScreenViewModel @Inject constructor() : ViewModel() {
    // ... 其他代码保持不变

    // 改为缓存的不可变列表
    private val areas = listOf(
        FunnelListItem(ITEM_AREA, 1, "Coimbatore BM"),
        FunnelListItem(ITEM_AREA, 2, "Madurai BM"),
        FunnelListItem(ITEM_AREA, 3, "Trichy BM"),
    )
    fun getAreas() = areas

    private val hQs = listOf(
        FunnelListItem(ITEM_HQ, 1, "Madurai 01"),
        FunnelListItem(ITEM_HQ, 2, "Madurai 02"),
    )
    fun getHQs() = hQs

    private val patches = listOf(
        FunnelListItem(ITEM_PATCH, 1, "Madurai"),
        FunnelListItem(ITEM_PATCH, 2, "Theni"),
    )
    fun getPatches() = patches

    private val customers = listOf(
        FunnelListItem(ITEM_CUSTOMER, 1, "Saravanan"),
        FunnelListItem(ITEM_CUSTOMER, 2, "Pandian"),
        FunnelListItem(ITEM_CUSTOMER, 3, "Anbu"),
        FunnelListItem(ITEM_CUSTOMER, 4, "Anbu"),
        FunnelListItem(ITEM_CUSTOMER, 5, "Anbu")
    )
    fun getCustomers() = customers
}

内容的提问来源于stack exchange,提问作者Indra Kumar S

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 19:57:32