Quản lý và trình bày trạng thái tải

Thư viện phân trang theo dõi trạng thái của các yêu cầu tải dữ liệu được phân trang và hiển thị thông qua lớp LoadState.

Bạn có thể cung cấp tín hiệu LoadState riêng biệt cho từng LoadType và loại nguồn dữ liệu (PagingSource hoặc RemoteMediator). Đối tượng CombinedLoadStates do trình nghe cung cấp cung cấp thông tin về trạng thái tải của tất cả tín hiệu này. Bạn có thể dùng thông tin chi tiết này để hiện các chỉ báo tải phù hợp cho người dùng.

Các trạng thái đang tải

Thư viện phân trang hiện trạng thái tải để sử dụng trong UI (giao diện người dùng) thông qua đối tượng LoadState. Các đối tượng LoadState có một trong ba dạng tuỳ thuộc vào trạng thái tải hiện tại:

  • Nếu không có hoạt động tải nào đang hoạt động và không có lỗi, thì LoadState là đối tượng LoadState.NotLoading. Lớp con này cũng bao gồm thuộc tính endOfPaginationReached, cho biết liệu đã đến cuối quá trình phân trang hay chưa.
  • Nếu có một hoạt động tải đang hoạt động, thì LoadState là đối tượng LoadState.Loading.
  • Nếu xảy ra lỗi thì LoadState là đối tượng LoadState.Error.

Truy cập vào các trạng thái này thông qua thuộc tính loadState của trình bao bọc LazyPagingItems. Bạn có thể sử dụng trạng thái này theo hai cách: xử lý khả năng hiển thị nội dung chính (như vòng quay làm mới toàn màn hình) hoặc chèn các mục tải trực tiếp vào luồng LazyColumn (như vòng quay chân trang).

Truy cập trạng thái đang tải bằng một trình nghe

Để theo dõi trạng thái đang tải trong giao diện người dùng, hãy sử dụng thuộc tính loadState do trình bao bọc LazyPagingItems cung cấp. Thuộc tính này trả về một CombinedLoadStates đối tượng cho phép bạn phản ứng với hành vi tải đối với các sự kiện làm mới, thêm hoặc thêm vào đầu.

Trong ví dụ sau, giao diện người dùng hiển thị một vòng quay tải hoặc một thông báo lỗi tuỳ thuộc vào trạng thái hiện tại của quá trình tải làm mới (ban đầu):

@Composable
fun UserListScreen(viewModel: UserViewModel) {
  val pagingItems = viewModel.flow.collectAsLazyPagingItems()

  Box(modifier = Modifier.fillMaxSize()) {
    // Show the list content
    LazyColumn {
      items(pagingItems.itemCount) { index ->
        UserItem(pagingItems[index])
      }
    }

    // Handle the loading state
    when (val state = pagingItems.loadState.refresh) {
      is LoadState.Loading -> {
        CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
      }
      is LoadState.Error -> {
        ErrorButton(
          message = state.error.message ?: "Unknown error",
          onClick = { pagingItems.retry() },
          modifier = Modifier.align(Alignment.Center)
        )
      }
      else -> {} // No separate view needed for success/not loading
    }
  }
}

Để biết thêm thông tin về LazyPagingItems, hãy xem Các tập dữ liệu lớn (phân trang).

Để hiển thị các chỉ báo tải ở đầu hoặc cuối danh sách (đóng vai trò là đầu trang hoặc chân trang), hãy thêm các khối mục dành riêng cho các trạng thái đó trong phạm vi LazyColumn.

Bạn có thể theo dõi trạng thái thêm vào đầu cho đầu trang và trạng thái thêm vào cuối cho chân trang bằng CombinedLoadStates đối tượng.

Trong ví dụ sau, danh sách hiển thị một thanh tiến trình hoặc một nút thử lại ở cuối danh sách khi đang tìm nạp thêm dữ liệu:

@Composable
fun UserList(viewModel: UserViewModel) {
  val pagingItems = viewModel.pager.flow.collectAsLazyPagingItems()

  LazyColumn {
    // 1. Header (Prepend state)
    // Useful if you support bidirectional paging or jumping to the middle
    item {
      val prependState = pagingItems.loadState.prepend
      if (prependState is LoadState.Loading) {
        LoadingItem()
      } else if (prependState is LoadState.Error) {
        ErrorItem(
          message = prependState.error.message ?: "Error",
          onClick = { pagingItems.retry() }
        )
      }
    }

    // 2. Main Data
    items(pagingItems.itemCount) { index ->
      UserItem(pagingItems[index])
    }

    // 3. Footer (Append state)
    // Shows when the user scrolls to the bottom and more data is loading
    item {
      val appendState = pagingItems.loadState.append
      if (appendState is LoadState.Loading) {
        LoadingItem()
      } else if (appendState is LoadState.Error) {
        ErrorItem(
          message = appendState.error.message ?: "Error",
          onClick = { pagingItems.retry() }
        )
      }
    }
  }
}

@Composable
fun LoadingItem() {
  Box(modifier = Modifier.fillMaxWidth().padding(16.dp), contentAlignment = Alignment.Center) {
    CircularProgressIndicator()
  }
}

@Composable
fun ErrorItem(message: String, onClick: () -> Unit) {
  Column(
    modifier = Modifier.fillMaxWidth().padding(16.dp),
    horizontalAlignment = Alignment.CenterHorizontally
  ) {
    Text(text = message, color = Color.Red)
    Button(onClick = onClick) { Text("Retry") }
  }
}

Truy cập các thông tin bổ sung về trạng thái đang tải

Như trong các ví dụ trước, việc gọi pagingItems.loadState.refresh rất tiện lợi. Tuy nhiên, thao tác này sẽ che khuất sự khác biệt giữa việc tải từ cơ sở dữ liệu cục bộ (PagingSource) và mạng (RemoteMediator). Điều này có thể khiến giao diện người dùng hiển thị nhanh một vòng quay tải ngay cả khi dữ liệu được lưu vào bộ nhớ đệm có sẵn ngay lập tức.

Để kiểm soát chính xác, chẳng hạn như chỉ hiển thị vòng quay tải khi cơ sở dữ liệu cục bộ trống và quá trình đồng bộ hoá mạng đang hoạt động, hãy truy cập trực tiếp vào các thuộc tính sourcemediator trong thành phần kết hợp.

val loadState = pagingItems.loadState

val isSyncing = loadState.mediator?.refresh is LoadState.Loading

val isLocalEmpty = loadState.source.refresh is LoadState.NotLoading &&
                   pagingItems.itemSnapshotList.items.isEmpty()

if (isSyncing && isLocalEmpty) {
    FullScreenLoading()
} else {
    UserList(pagingItems)

    if (isSyncing) {
        TopOverlaySpinner()
    }
}

Phản ứng với các thay đổi về trạng thái tải

Bạn có thể cần kích hoạt các hiệu ứng phụ một lần dựa trên các thay đổi về trạng thái tải, chẳng hạn như cuộn lên đầu danh sách hoặc hiển thị Snackbar khi quá trình làm mới hoàn tất.

Sử dụng snapshotFlow bên trong LaunchedEffect để quan sát các thay đổi về trạng thái dưới dạng luồng. Điều này cho phép bạn áp dụng các toán tử tiêu chuẩn Flow như filterdistinctUntilChanged để tách biệt các sự kiện cụ thể.

val listState = rememberLazyListState()

LaunchedEffect(pagingItems) {
  // 1. Convert the state to a Flow
  snapshotFlow { pagingItems.loadState.refresh }
    // 2. Filter for the specific event (Refresh completed successfully)
    .distinctUntilChanged()
    .filter { it is LoadState.NotLoading }
    .collect {
      // 3. Trigger the side effect
      listState.animateScrollToItem(0)
    }
}

Tài nguyên khác

Để biết thêm thông tin về thư viện Phân trang và trạng thái tải, hãy tham khảo các tài nguyên sau.

Tài liệu

Nội dung khung hiển thị