Jetpack Compose中LazyColumn多项同时点击下载文件冲突问题
解决Jetpack Compose多列表项下载互相干扰的问题
问题描述
在Jetpack Compose中实现了列表点击下载功能,点击列表项可下载关联文件,但同时点击多个列表项触发下载时,各下载操作会互相干扰,导致状态混乱。
原ViewModel代码:
class BusinessViewModel(private val userRepository: UserRepository) : ViewModel() { val downloadSqliteDatabaseData: MutableState<State<String>> = mutableStateOf(State.Empty) fun downloadSqliteDatabase(file: File?, businessId: Long) { viewModelScope.launch { userRepository.downloadSqliteDatabase(businessId).onStart { downloadSqliteDatabaseData.value = State.Loading }.catch { downloadSqliteDatabaseData.value = State.Error("ERROR", MyHttpStatusCode.NetworkError) }.collect { downloadSqliteDatabaseData.value = when (it.status) { HttpStatusCode.OK -> { try { val data: ByteArray = it.body() file?.writeBytes(data) State.Success("", it.status) } catch (ex: Exception) { State.Error("ERROR", MyHttpStatusCode.ConvertDataError) } } MyHttpStatusCode.InvalidUser -> { State.Error("ERROR", it.status) } else -> { State.Error("ERROR", it.status) } } } } } }
原Compose函数代码:
val downloadSqliteDatabaseData by remember { viewModel.downloadSqliteDatabaseData } DisposableEffect(downloadSqliteDatabaseData) { when (downloadSqliteDatabaseData) { is State.Empty -> {} is State.Loading -> { //downloadDatabaseLoading.value = true } is State.Success -> { //downloadDatabaseLoading.value = false } is State.Error -> { println("Error") //downloadDatabaseLoading.value = false } } onDispose { } } LazyColumn( state = lazyListState, modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally ) { itemsIndexed( items = businessList, key = { _, item -> item.id }, ) { index, item -> ClickBox( modifier = Modifier .padding(start = 15.dp, end = 15.dp) .fillMaxWidth() .height(120.dp), onClick = { val dbPath = File("test.txt") viewModel.downloadSqliteDatabase(dbPath, item.id) } ) { //.... } } }
问题原因
核心问题在于ViewModel中使用了单个全局状态变量downloadSqliteDatabaseData来管理所有下载任务的状态。当多个下载同时触发时,后续下载的状态更新会覆盖之前的状态,导致所有下载任务共用同一状态,UI无法区分不同任务的进度,最终造成操作互相干扰。
解决方案
为每个下载任务维护独立的状态,通过StateMap(或MutableStateMap)以businessId为键,存储对应任务的下载状态,确保各任务状态互不影响。
修改后的ViewModel代码
class BusinessViewModel(private val userRepository: UserRepository) : ViewModel() { // 用businessId作为键,存储每个下载任务的独立状态 private val _downloadStates = mutableStateMapOf<Long, State<String>>() val downloadStates: StateMap<Long, State<String>> = _downloadStates fun downloadSqliteDatabase(file: File?, businessId: Long) { viewModelScope.launch { // 初始化为Loading状态 _downloadStates[businessId] = State.Loading userRepository.downloadSqliteDatabase(businessId) .catch { _downloadStates[businessId] = State.Error("ERROR", MyHttpStatusCode.NetworkError) } .collect { response -> _downloadStates[businessId] = when (response.status) { HttpStatusCode.OK -> { try { val data: ByteArray = response.body() file?.writeBytes(data) State.Success("", response.status) } catch (ex: Exception) { State.Error("ERROR", MyHttpStatusCode.ConvertDataError) } } MyHttpStatusCode.InvalidUser -> { State.Error("ERROR", response.status) } else -> { State.Error("ERROR", response.status) } } } } } }
修改后的Compose函数代码
LazyColumn( state = lazyListState, modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally ) { itemsIndexed( items = businessList, key = { _, item -> item.id }, ) { index, item -> // 获取当前列表项对应的下载状态,默认Empty val downloadState = viewModel.downloadStates[item.id] ?: State.Empty // 根据当前项的状态处理UI逻辑 DisposableEffect(downloadState) { when (downloadState) { is State.Loading -> { // 当前项的加载逻辑,比如显示加载动画 } is State.Success -> { // 当前项下载成功逻辑,比如提示用户 } is State.Error -> { println("Business ${item.id} 下载失败: ${downloadState.message}") // 当前项下载失败逻辑,比如显示错误提示 } else -> {} } onDispose {} } ClickBox( modifier = Modifier .padding(start = 15.dp, end = 15.dp) .fillMaxWidth() .height(120.dp), onClick = { val dbPath = File("test_${item.id}.txt") // 避免所有下载写入同一文件,按businessId命名文件 viewModel.downloadSqliteDatabase(dbPath, item.id) } ) { // 列表项UI,可根据downloadState显示不同状态,比如加载中按钮置灰等 } } }
额外优化点
- 文件命名:原代码中所有下载都写入
test.txt,会导致文件被覆盖,建议根据businessId生成唯一文件名。 - 状态清理:如果列表项会被频繁刷新或销毁,可在合适时机从
downloadStates中移除已完成(成功/失败)的状态,避免内存占用。
内容的提问来源于stack exchange,提问作者Mohammad Mohammadi
相关产品推荐
相关产品推荐

