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 sourcemediator 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 snapshotFlowLaunchedEffect, aby obserwować zmiany stanu jako strumień. Umożliwia to stosowanie standardowych operatorów Flow, takich jak filterdistinctUntilChanged, 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