Jetpack compose CircularProgressIndicator 在 api 获取时滞后

Jetpack compose CircularProgressIndicator lag on api fetch

我想在从 API 下载数据时显示加载指示器。但是,当发生这种情况时,指标通常会停止。我怎样才能改变这个或者什么可能是错的?基本上,我获取出发时间并对其进行处理(例如,我将十六进制颜色转换为 Jetpack Compose 颜色,或将 unix 日期转换为日期类型等),然后将它们加载到列表中并显示它们。

@Composable
fun StopScreen(
    unixDate: Long? = null,
    stopId: String,
    viewModel: MainViewModel = hiltViewModel()
) {
    LaunchedEffect(Unit) {
        viewModel.getArrivalsAndDeparturesForStop(
            unixDate,
            stopId,
            false
        )
    }
    val isLoading by remember { viewModel.isLoading }
    if (!isLoading) {
        //showData
    } else {
        LoadingView()
    }
}


@Composable
fun LoadingView() {
    Box(
        contentAlignment = Alignment.Center,
        modifier = Modifier.fillMaxSize()
    ) {
        CircularProgressIndicator(color = MaterialTheme.colors.primary)
    }
}

以及我处理数据的视图模型:

@HiltViewModel
class MainViewModel @Inject constructor(
    private val mainRepository: MainRepository
) : ViewModel() {
    var stopTimesList = mutableStateOf<MutableList<StopTime>>(arrayListOf())
    var alertsList = mutableStateOf<MutableList<Alert>>(arrayListOf())
    var loadError = mutableStateOf("")
    var isLoading = mutableStateOf(false)
    var isRefreshing = mutableStateOf(false)
    fun getArrivalsAndDeparturesForStop(unixDate: Long? = null, stopId: String, refresh: Boolean) {
        viewModelScope.launch {
            if (refresh) {
                isRefreshing.value = true
            } else {
                isLoading.value = true
            }
            val result = mainRepository.getArrivalsAndDeparturesForStop(stopId = stopId, time = unixDate)
            when (result) {
                is Resource.Success -> {
                    //I temporarily store the data here, so that the screen is only refreshed on reload when all the new data has arrived and loaded 
                    var preStopTimes: MutableList<StopTime> = arrayListOf()
                    var preAlertsList: MutableList<Alert> = arrayListOf()
                    if (result.data!!.stopTimes != null && result.data!!.alerts != null) {
                        var count = 0
                        val countAll =
                            result.data!!.stopTimes!!.count() + result.data!!.alertIds!!.count()
                        if (countAll == 0) {
                            loadError.value = ""
                            isLoading.value = false
                            isRefreshing.value = false
                        }
                        //ALERTS
                        for (alert in result.data!!.data.alerts) {
                            preAlertsList.add(alert)
                            count += 1
                            if (count == countAll) {
                                stopTimesList.value = preStopTimes
                                alertsList.value = preAlertsList
                                loadError.value = ""
                                isLoading.value = false
                                isRefreshing.value = false
                            }
                        }
                        for (stopTime in result.data!!.stopTimes!!) {
                            preStopTimes.add(stopTime)
                            count += 1
                            if (count == countAll) {
                                stopTimesList.value = preStopTimes
                                alertsList.value = preAlertsList
                                loadError.value = ""
                                isLoading.value = false
                                isRefreshing.value = false
                            }
                        }
                    } else {
                        loadError.value = "Error"
                        isLoading.value = false
                        isRefreshing.value = false
                    }
                }
                is Resource.Error -> {
                    loadError.value = result.message!!
                    isLoading.value = false
                    isRefreshing.value = false
                }
            }
        }
    }
}

存储库:

@ActivityScoped
class MainRepository @Inject constructor(
    private val api: MainApi
) {
    suspend fun getArrivalsAndDeparturesForStop(stopId: String,time: Long? = null): Resource<ArrivalsAndDeparturesForStop> {
        val response = try {
            api.getArrivalsAndDeparturesForStop(
                stopId,
                time
            )
        } catch (e: Exception) { return Resource.Error(e.message!!)}
        return Resource.Success(response)
    }
}

我的看法是您的 Composable 重构过于频繁。因为你在你的 for 循环中更新你的状态。否则,可能是因为 MainRepository 中的挂起方法未在正确的线程中调度。

我觉得您还没有掌握 Compose 的内部工作原理(没关系,反正这是一个新话题)。我建议提升一个独特的状态,而不是为您的所有属性设置多个可变状态。然后在您的 VM 内部构建它,然后在状态更改时通知视图。

像这样:

data class YourViewState(
    val stopTimesList: List<StopTime> = emptyList(),
    val alertsList: List<Alert> = emptyList(),
    val isLoading: Boolean = false,
    val isRefreshing: Boolean = false,
    val loadError: String? = null,
)

@HiltViewModel
class MainViewModel @Inject constructor(
    private val mainRepository: MainRepository
) : ViewModel() {

    var viewState by mutableStateOf<YourViewState>(YourViewState())

    fun getArrivalsAndDeparturesForStop(unixDate: Long? = null, stopId: String, refresh: Boolean) {
        viewModelScope.launch {
            viewState = if (refresh) {
                viewState.copy(isRefreshing = true)
            } else {
                viewState.copy(isLoading = true)
            }

            when (val result = mainRepository.getArrivalsAndDeparturesForStop(stopId = stopId, time = unixDate)) {
                is Resource.Success -> {
                    //I temporarily store the data here, so that the screen is only refreshed on reload when all the new data has arrived and loaded
                    val preStopTimes: MutableList<StopTime> = arrayListOf()
                    val preAlertsList: MutableList<Alert> = arrayListOf()
                    if (result.data!!.stopTimes != null && result.data!!.alerts != null) {
                        var count = 0
                        val countAll = result.data!!.stopTimes!!.count() + result.data!!.alertIds!!.count()
                        if (countAll == 0) {
                            viewState = viewState.copy(isLoading = false, isRefreshing = false)
                        }
                        //ALERTS
                        for (alert in result.data!!.data.alerts) {
                            preAlertsList.add(alert)
                            count += 1
                            if (count == countAll) {
                                break
                            }
                        }
                        for (stopTime in result.data!!.stopTimes!!) {
                            preStopTimes.add(stopTime)
                            count += 1
                            if (count == countAll) {
                                break
                            }
                        }
                        viewState = viewState.copy(isLoading = false, isRefreshing = false, stopTimesList = preStopTimes, alertsList = preAlertsList)
                    } else {
                        viewState = viewState.copy(isLoading = false, isRefreshing = false, loadError = "Error")
                    }
                }
                is Resource.Error -> {
                    viewState = viewState.copy(isLoading = false, isRefreshing = false, loadError = result.message!!)
                }
            }
        }
    }
}

@Composable
fun StopScreen(
    unixDate: Long? = null,
    stopId: String,
    viewModel: MainViewModel = hiltViewModel()
) {
    LaunchedEffect(Unit) {
        viewModel.getArrivalsAndDeparturesForStop(
            unixDate,
            stopId,
            false
        )
    }
    if (viewModel.viewState.isLoading) {
        LoadingView()
    } else {
        //showData
    }
}

请注意,我在保持原始结构的同时做了一些改进。

编辑:

您需要从 MainRepository main-safe 中创建您的挂起方法。它很可能在主线程(调用者线程)上运行,因为您没有指定协程在哪个调度程序上运行。

suspend fun getArrivalsAndDeparturesForStop(stopId: String,time: Long? = null): Resource<ArrivalsAndDeparturesForStop> = withContext(Dispatchers.IO) {
        try {
            api.getArrivalsAndDeparturesForStop(
                stopId,
                time
            )
            Resource.Success(response)
        } catch (e: Exception) {
            Resource.Error(e.message!!)
        }