基于Jetpack Compose LazyColumn的游标分页MVI实现求助
LazyColumn 游标分页(MVI架构实现)
1. 定义MVI状态(State)
明确页面核心状态,包含交易数据、分页标识、加载状态,同时避免重复请求:
sealed class LoadState { object Idle : LoadState() object LoadingFirstPage : LoadState() object LoadingNext : LoadState() object LoadingPrev : LoadState() data class Error(val message: String) : LoadState() } data class TransfersState( val transactions: List<TransactionDataDto>, val paging: PagingDto, val loadState: LoadState, val isLoadingNext: Boolean = false, val isLoadingPrev: Boolean = false ) { companion object { val Initial = TransfersState( transactions = emptyList(), paging = PagingDto(next = null, prev = null), loadState = LoadState.Idle ) } }
2. 定义用户意图(Intent)
封装UI层触发的所有操作:
sealed class TransfersIntent { object LoadFirstPage : TransfersIntent() object LoadNextPage : TransfersIntent() object CheckAndLoadPrevPage : TransfersIntent() }
3. Repository层实现
封装API请求逻辑,根据参数返回分页数据:
interface TransfersRepository { suspend fun getTransfers( take: Int = 20, after: String? = null, before: String? = null ): Result<TransfersListResponse> } class TransfersRepositoryImpl(private val apiService: ApiService) : TransfersRepository { override suspend fun getTransfers(take: Int, after: String?, before: String?): Result<TransfersListResponse> { return runCatching { apiService.getTransfers(take = take, after = after, before = before) } } }
4. ViewModel核心逻辑处理
处理意图并更新状态,严格遵循需求中的分页规则:
class TransfersViewModel( private val repository: TransfersRepository ) : ViewModel() { private val _state = MutableStateFlow(TransfersState.Initial) val state = _state.asStateFlow() fun handleIntent(intent: TransfersIntent) { when (intent) { is TransfersIntent.LoadFirstPage -> loadFirstPage() is TransfersIntent.LoadNextPage -> loadNextPage() is TransfersIntent.CheckAndLoadPrevPage -> checkAndLoadPrevPage() } } private fun loadFirstPage() { viewModelScope.launch { _state.update { it.copy(loadState = LoadState.LoadingFirstPage) } val result = repository.getTransfers() result.onSuccess { response -> _state.update { it.copy( transactions = response.items, paging = response.paging, loadState = LoadState.Idle ) } }.onFailure { _state.update { it.copy(loadState = LoadState.Error(it.message ?: "加载失败")) } } } } private fun loadNextPage() { val currentState = _state.value if (currentState.paging.next == null || currentState.isLoadingNext) return viewModelScope.launch { _state.update { it.copy(isLoadingNext = true) } val result = repository.getTransfers(after = currentState.paging.next) result.onSuccess { response -> _state.update { it.copy( transactions = it.transactions + response.items, paging = response.paging, isLoadingNext = false ) } }.onFailure { _state.update { it.copy( isLoadingNext = false, loadState = LoadState.Error(it.message ?: "加载下一页失败") ) } } } } private fun checkAndLoadPrevPage() { val currentState = _state.value if (currentState.isLoadingPrev) return viewModelScope.launch { _state.update { it.copy(isLoadingPrev = true) } // 重新加载第一页检查prev状态 val firstPageResult = repository.getTransfers() firstPageResult.onSuccess { firstPageResponse -> firstPageResponse.paging.prev?.let { prevId -> // prev存在则加载上一页数据 val prevPageResult = repository.getTransfers(before = prevId) prevPageResult.onSuccess { prevPageResponse -> _state.update { it.copy( transactions = prevPageResponse.items + it.transactions, paging = firstPageResponse.paging.copy(prev = prevPageResponse.paging.prev), isLoadingPrev = false ) } }.onFailure { _state.update { it.copy( isLoadingPrev = false, loadState = LoadState.Error(it.message ?: "加载上一页失败") ) } } } ?: run { // prev为空仅更新分页状态 _state.update { it.copy( paging = firstPageResponse.paging, isLoadingPrev = false ) } } }.onFailure { _state.update { it.copy( isLoadingPrev = false, loadState = LoadState.Error(it.message ?: "检查顶部分页失败") ) } } } } }
5. UI层实现(LazyColumn)
处理滚动监听,触发对应意图,展示加载状态:
@Composable fun TransfersScreen(viewModel: TransfersViewModel = viewModel()) { val state by viewModel.state.collectAsState() val lazyListState = rememberLazyListState() // 滚动到顶部时触发顶部分页检查 LaunchedEffect(lazyListState.firstVisibleItemIndex) { if (lazyListState.firstVisibleItemIndex == 0 && state.transactions.isNotEmpty()) { viewModel.handleIntent(TransfersIntent.CheckAndLoadPrevPage) } } // 滚动到底部时触发下一页加载 LaunchedEffect(lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()) { val lastVisibleItem = lazyListState.layoutInfo.visibleItemsInfo.lastOrNull() if (lastVisibleItem?.index == state.transactions.size - 1 && state.paging.next != null) { viewModel.handleIntent(TransfersIntent.LoadNextPage) } } when (state.loadState) { is LoadState.LoadingFirstPage -> { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator() } } is LoadState.Error -> { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text((state.loadState as LoadState.Error).message) } } else -> { LazyColumn(state = lazyListState) { // 顶部加载占位 if (state.isLoadingPrev) { item { Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { CircularProgressIndicator(modifier = Modifier.padding(16.dp)) } } } items(state.transactions) { transaction -> TransactionItem(transaction) } // 底部加载占位 if (state.isLoadingNext) { item { Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { CircularProgressIndicator(modifier = Modifier.padding(16.dp)) } } } } } } // 页面初始化加载第一页 LaunchedEffect(Unit) { viewModel.handleIntent(TransfersIntent.LoadFirstPage) } } @Composable fun TransactionItem(transaction: TransactionDataDto) { Card(modifier = Modifier.fillMaxWidth().padding(8.dp)) { Text(text = transaction.toString(), modifier = Modifier.padding(16.dp)) } }
关键逻辑说明
- 首次加载:仅传递
take参数获取第一页,prev为null,next返回分页ID - 加载下一页:滚动到底部时,用当前
next作为after参数请求,新数据追加到列表末尾 - 顶部检查:滚动到顶部时重新请求第一页,若
prev不为null则用before参数请求上一页,新数据插入列表头部 - 防重复请求:通过
isLoadingNext和isLoadingPrev标记,避免同一请求重复触发
内容的提问来源于stack exchange,提问作者tasjapr
相关产品推荐
相关产品推荐

