Zarządzaj stanami wczytywania i wyświetlaj je

Biblioteka Paging śledzi stan żądań wczytywania danych podzielonych na strony i udostępnia go za pomocą klasy LoadState.

Dla każdego LoadState sygnału udostępniany jest oddzielny sygnał LoadType i typ źródła danych (PagingSource lub RemoteMediator). Obiekt CombinedLoadStates udostępniany przez odbiornik zawiera informacje o stanie wczytywania ze wszystkich tych sygnałów. Możesz użyć tych szczegółowych informacji, aby wyświetlać użytkownikom odpowiednie wskaźniki wczytywania.

Wczytuję stany

Biblioteka Paging udostępnia stan wczytywania do użycia w interfejsie za pomocą obiektu LoadState. LoadState obiekty przyjmują jedną z 3 form w zależności od bieżącego stanu wczytywania:

  • Jeśli nie ma aktywnej operacji wczytywania i nie wystąpił żaden błąd, LoadState jest obiektem LoadState.NotLoading. Ta podklasa zawiera też właściwość endOfPaginationReached, która wskazuje, czy osiągnięto koniec paginacji.
  • Jeśli trwa aktywna operacja wczytywania, LoadState jest obiektem LoadState.Loading.
  • Jeśli wystąpi błąd, LoadState jest obiektem LoadState.Error.

Dostęp do tych stanów uzyskasz za pomocą właściwości loadState elementu opakowującego LazyPagingItems. Ten stan możesz wykorzystać na 2 sposoby: do obsługi widoczności głównej treści (np. pełnoekranowego wskaźnika odświeżania) lub do wstawiania elementów wczytywania bezpośrednio do strumienia LazyColumn (np. wskaźnika w stopce).

Dostęp do stanu wczytywania za pomocą detektora

Aby monitorować stan wczytywania w interfejsie, użyj właściwości loadState udostępnianej przez komponent LazyPagingItems. Zwraca obiekt CombinedLoadStates, który umożliwia reagowanie na zachowanie podczas wczytywania w przypadku zdarzeń odświeżania, dołączania lub dodawania na początku.

W poniższym przykładzie interfejs wyświetla wskaźnik postępu wczytywania lub komunikat o błędzie w zależności od bieżącego stanu odświeżania (początkowego wczytywania):

@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
    }
  }
}

Więcej informacji o funkcji LazyPagingItems znajdziesz w artykule Duże zbiory danych (stronicowanie).

Aby wyświetlać wskaźniki ładowania na początku lub na końcu listy (jako nagłówki lub stopki), dodaj bloki elementów przeznaczone specjalnie dla tych stanów w zakresie LazyColumn.

Stan dodawania na początku nagłówka i na końcu stopki możesz monitorować za pomocą obiektu CombinedLoadStates.

W tym przykładzie lista wyświetla pasek postępu lub przycisk ponowienia u dołu, gdy pobierane są kolejne dane:

@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") }
  }
}

Dostęp do dodatkowych informacji o stanie wczytywania

Jak pokazują wcześniejsze przykłady, dzwonienie pod numer pagingItems.loadState.refresh jest wygodne. Zaciera to jednak różnicę między wczytywaniem z lokalnej bazy danych (PagingSource) a wczytywaniem z sieci (RemoteMediator). Może to spowodować, że interfejs użytkownika będzie przez chwilę wyświetlać spinner ładowania, nawet jeśli dane z pamięci podręcznej są od razu dostępne.

Aby uzyskać precyzyjną kontrolę, np. wyświetlać wskaźnik ładowania tylko wtedy, gdy lokalna baza danych jest pusta i aktywna jest synchronizacja z siecią, uzyskaj dostęp do właściwości source i mediator bezpośrednio w funkcji kompozycyjnej.

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()
    }
}

Reagowanie na zmiany stanu wczytywania

Możesz potrzebować jednorazowych efektów ubocznych wywoływanych przez zmiany stanu wczytywania, np. przewijania listy do góry lub wyświetlania Snackbar po zakończeniu odświeżania.

Użyj snapshotFlow w LaunchedEffect, aby obserwować zmiany stanu jako strumień. Umożliwia to stosowanie standardowych operatorów Flow, takich jak filter i distinctUntilChanged, do wyodrębniania konkretnych zdarzeń.

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)
    }
}

Dodatkowe materiały

Więcej informacji o bibliotece Paging i stanach wczytywania znajdziesz w tych materiałach.

Dokumentacja

Wyświetlanie treści