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

Kotlin协程Flow收集电影数据重复累加问题咨询

问题结论

不需要把ViewModel调用逻辑移到Activity中,数据重复累加的问题和调用位置无关,完全是现有代码的状态管理、数据收集逻辑写错导致的。

问题根源
  • 核心错误在ViewModel的收集逻辑:每次Flow每隔5秒返回新的全量电影列表时,你调用addAll()往原有列表里追加数据,还把清空列表的代码注释掉了,自然首次3条、第二次追加3条变6条、第三次变9条。
  • 现有状态管理不符合Compose规范:你直接对外暴露可变MutableList,列表内部元素变化不会触发Compose重组,后续做收藏、详情交互时会出现UI不更新的问题。
  • Repo层的Flow存在泄漏风险:用while(true)无限循环没有判断协程活跃状态,页面销毁、协程取消时循环不会自动停止,还没有加网络异常兜底。
修正后的实现代码

1. Repo层修正

// 不需要给方法加suspend修饰,flow构建器本身是非阻塞的
override fun getPopularMovies() : Flow<List<Movie>> = flow{
    // 用isActive替换while(true),协程取消时自动退出循环,避免资源泄漏
    while(currentCoroutineContext().isActive){
        try {
            val latestMovies = movieApi.getPopularMovies()
            emit(latestMovies)
        } catch (e: Exception) {
            // 自行处理网络请求异常,比如打日志、兜底错误状态
            Log.e("MovieRepo", "获取热门电影失败", e)
        }
        delay(5000)
    }
}

2. ViewModel层修正

class HomeViewModel(private val repository: MovieRepo): ViewModel() {
    // 内部用可变StateFlow持有状态,初始值为空列表
    private val _popularMovies = MutableStateFlow<List<Movie>>(emptyList())
    // 对外暴露不可变StateFlow,禁止UI层直接修改数据
    val popularMovies: StateFlow<List<Movie>> = _popularMovies.asStateFlow()

    init{
        viewModelScope.launch {
            repository.getPopularMovies().collect{ newMovieList ->
                // 每次拿到新的全量列表直接替换旧数据,不要addAll追加
                _popularMovies.value = newMovieList
            }
        }
    }

    // 后续交互逻辑都放在ViewModel里,比如切换收藏状态
    fun toggleFavorite(movieId: Int) {
        // 在这里处理收藏状态修改,更新_stateFlow的值即可
    }
}

注意:不需要给协程指定Dispatchers.Default,Retrofit的网络请求本身会自动切换IO线程,Flow收集不会阻塞主线程。

3. Compose页面修正

@Composable
fun HomeScreen() {
    val viewModel = getViewModel<HomeViewModel>()
    // 用生命周期感知的方式收集Flow,页面不可见时自动停止收集,节省资源
    val popularMovies by viewModel.popularMovies.collectAsStateWithLifecycle()

    // 这里拿到的popularMovies是不可变列表,直接传给子组件即可
    // 交互事件通过传方法的方式回调给ViewModel,不要把可变列表传给子组件
    // MovieList(
    //     movies = popularMovies,
    //     onMovieClick = { movie -> /* 跳转详情逻辑 */ },
    //     onFavoriteClick = { movieId -> viewModel.toggleFavorite(movieId) }
    // )
}
补充说明

通过getViewModel()在Composable中获取的ViewModel,和在Activity中获取的是同作用域的同一个实例,不存在调用位置导致的数据异常问题。后续做交互时遵循「状态向下传递、事件向上回调」的Compose状态管理原则即可,不要把可变集合直接暴露给UI层,所有数据修改逻辑收敛到ViewModel中,就不会出现数据错乱、UI不刷新的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 10:36:38