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

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显示不同状态,比如加载中按钮置灰等
        }
    }
}

额外优化点

  1. 文件命名:原代码中所有下载都写入test.txt,会导致文件被覆盖,建议根据businessId生成唯一文件名。
  2. 状态清理:如果列表项会被频繁刷新或销毁,可在合适时机从downloadStates中移除已完成(成功/失败)的状态,避免内存占用。

内容的提问来源于stack exchange,提问作者Mohammad Mohammadi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 17:25:00