Composable状态变更时isLoading未更新,CircularProgressIndicator不显示
问题:Compose中CircularProgressIndicator无法显示,Loading状态未更新
我定义了isLoading状态,期望其值为true时显示CircularProgressIndicator,对应的Composable代码如下:
@Composable fun ProductDetailScreen( viewModel: ProductDetailViewModel = hiltViewModel() ) { val productState = viewModel.productState.value LazyColumn{ item { if (productState.isLoading) CircularProgressIndicator() } } }
我使用Resource类封装API调用结果,在Repository中用该类包装请求结果。问题在于,尽管Repository返回了Resource.Loading,但ViewModel中的isLoading状态并未更新,导致屏幕上的CircularProgressIndicator无法显示。
相关代码
Resource类
sealed class Resource<T>( val data: T? = null, val message: String? = null, val errorType: ExceptionMapper.Type? = null ) { class Success<T>(data: T?) : Resource<T>(data) class Error<T>(message: String, errorType: ExceptionMapper.Type, data: T? = null) : Resource<T>(data, message, errorType) class Loading<T>(isLoading: Boolean = true) : Resource<T>() }
Repository
override suspend fun getProductComments(productId: Int): Resource<List<Comment>> { return try { Resource.Loading<List<Comment>>() delay(3000) Resource.Success(apiService.getComments(productId)) } catch (t: Throwable) { val mappedException = ExceptionMapper.map(t) Resource.Error(message = t.message!!, errorType = mappedException.type) } }
ViewModel
@HiltViewModel class ProductDetailViewModel @Inject constructor( state: SavedStateHandle, private val productRepository: ProductRepository ) : ViewModel() { private val passedProduct = state.get<Product>(EXTRA_KEY_DATA) var productId = passedProduct?.id var productState = mutableStateOf(ProductState()) private set init { getProductComments() } private fun getProductComments() { viewModelScope.launch { productId?.let { pId -> when (val commentResult = productRepository.getProductComments(pId)) { is Resource.Success -> { commentResult.data?.let { comments -> productState.value = productState.value.copy( comments = comments, error = null, isLoading = false ) } } is Resource.Error -> { productState.value = productState.value.copy( isLoadFailed = true, isLoading = false, error = commentResult.message ) } is Resource.Loading -> { productState.value = productState.value.copy( isLoadFailed = false, isLoading = true, error = null ) } } } } } }
问题原因分析
Repository中Loading状态未实际传递
你的Repository方法里,Resource.Loading<List<Comment>>()只是创建了一个实例,但没有返回它,紧接着执行delay(3000)后直接返回了Resource.Success。单个suspend函数只能返回一个值,所以Loading状态根本没有传递到ViewModel。单个返回值无法承载多状态
要在请求流程中依次发送Loading、Success/Error多个状态,不能用普通suspend函数返回单个Resource对象,必须使用Flow来发射多个状态值。
修正方案
1. 修改Repository,改用Flow发射状态
override fun getProductComments(productId: Int): Flow<Resource<List<Comment>>> = flow { // 先发射Loading状态 emit(Resource.Loading<List<Comment>>()) try { delay(3000) val comments = apiService.getComments(productId) // 发射Success状态 emit(Resource.Success(comments)) } catch (t: Throwable) { val mappedException = ExceptionMapper.map(t) // 发射Error状态 emit(Resource.Error(message = t.message!!, errorType = mappedException.type)) } }.flowOn(Dispatchers.IO)
2. 修改ViewModel,收集Flow更新状态
@HiltViewModel class ProductDetailViewModel @Inject constructor( state: SavedStateHandle, private val productRepository: ProductRepository ) : ViewModel() { private val passedProduct = state.get<Product>(EXTRA_KEY_DATA) var productId = passedProduct?.id var productState = mutableStateOf(ProductState()) private set init { getProductComments() } private fun getProductComments() { productId?.let { pId -> viewModelScope.launch { productRepository.getProductComments(pId).collect { result -> when (result) { is Resource.Success -> { result.data?.let { comments -> productState.value = productState.value.copy( comments = comments, error = null, isLoading = false ) } } is Resource.Error -> { productState.value = productState.value.copy( isLoadFailed = true, isLoading = false, error = result.message ) } is Resource.Loading -> { productState.value = productState.value.copy( isLoadFailed = false, isLoading = true, error = null ) } } } } } } }
3. 可选:优化Resource类(移除无用参数)
你的Loading类中的isLoading参数未被使用,可直接移除:
sealed class Resource<T>( val data: T? = null, val message: String? = null, val errorType: ExceptionMapper.Type? = null ) { class Success<T>(data: T?) : Resource<T>(data) class Error<T>(message: String, errorType: ExceptionMapper.Type, data: T? = null) : Resource<T>(data, message, errorType) class Loading<T> : Resource<T>() }
内容的提问来源于stack exchange,提问作者Hamed
相关产品推荐
相关产品推荐

