android Jetpack合成循环进度指示器在API获取上滞后

f87krz0w  于 2022-11-20  发布在  Android
关注(0)|答案(2)|浏览(215)

当我从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)
    }
}
k5ifujac

k5ifujac1#

我认为你的Composable程序重组太频繁了,因为你在for循环中更新状态,否则,可能是因为你的MainRepository中的suspend方法没有在正确的线程中调度。
我觉得您还没有掌握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的suspend方法是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!!)
        }
wgeznvg7

wgeznvg72#

6个月后,我找到了确切的解决方案。当我在ViewModel中处理数据时,所有的东西都在主线程上运行。进一步研究一下,我应该在函数中使用Dispatchers.Default和/或Dispatchers.IO来完成CPU密集型/列表排序/ JSON解析任务。
https://developer.android.com/kotlin/coroutines/coroutines-adv

suspend fun doSmg() {
            withContext(Dispatchers.IO) {
                //This dispatcher is optimized to perform disk or network I/O outside of the main thread. Examples include using the Room component, reading from or writing to files, and running any network operations.
            }
            withContext(Dispatchers.Default) {
                //This dispatcher is optimized to perform CPU-intensive work outside of the main thread. Example use cases include sorting a list and parsing JSON.
            }
    }

相关问题