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!!)
}
我想在从 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!!)
}