Управляйте и отображайте состояния загрузки

Библиотека Paging отслеживает состояние запросов на загрузку постраничных данных и предоставляет к ним доступ через класс LoadState .

Для каждого LoadType и типа источника данных ( PagingSource или RemoteMediator ) предоставляется отдельный сигнал LoadState . Объект CombinedLoadStates , предоставляемый слушателем, содержит информацию о состоянии загрузки из всех этих сигналов. Вы можете использовать эту подробную информацию для отображения соответствующих индикаторов загрузки вашим пользователям.

Состояния загрузки

Библиотека Paging предоставляет доступ к состоянию загрузки для использования в пользовательском интерфейсе через объект LoadState . Объекты LoadState принимают одну из трех форм в зависимости от текущего состояния загрузки:

  • Если активной операции загрузки нет и ошибок нет, то LoadState представляет собой объект LoadState.NotLoading . Этот подкласс также включает свойство endOfPaginationReached , которое указывает, достигнут ли конец пагинации.
  • Если выполняется активная операция загрузки, то LoadState представляет собой объект LoadState.Loading .
  • В случае ошибки, LoadState будет иметь вид объекта LoadState.Error .

Доступ к этим состояниям осуществляется через свойство loadState вашего обертки LazyPagingItems . Вы можете использовать это состояние двумя способами: для управления видимостью основного контента (например, как индикатор обновления на весь экран) или для вставки элементов загрузки непосредственно в поток LazyColumn (например, индикатор загрузки в нижнем колонтитуле).

Доступ к состоянию загрузки осуществляется с помощью слушателя.

Для отслеживания состояния загрузки в пользовательском интерфейсе используйте свойство loadState предоставляемое оберткой LazyPagingItems . Оно возвращает объект CombinedLoadStates , который позволяет реагировать на события загрузки: обновление, добавление или добавление элементов.

В следующем примере пользовательский интерфейс отображает индикатор загрузки или сообщение об ошибке в зависимости от текущего состояния обновления (начальной) загрузки:

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

Для получения дополнительной информации о LazyPagingItems см. раздел «Большие наборы данных (постраничная навигация)» .

Чтобы отображать индикаторы загрузки в начале или конце списка (в качестве заголовков или нижних колонтитулов), добавьте специальные блоки элементов, предназначенные именно для этих состояний, в рамках области видимости LazyColumn .

Отслеживать состояние добавления заголовка (prepend) и состояние добавления нижнего колонтитула (uppend) можно с помощью объекта CombinedLoadStates .

В следующем примере при загрузке дополнительных данных внизу списка отображается индикатор выполнения или кнопка повторной попытки:

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

Получите доступ к дополнительной информации о состоянии загрузки.

Как показано в предыдущих примерах, вызов pagingItems.loadState.refresh удобен. Однако он скрывает разницу между загрузкой из локальной базы данных ( PagingSource ) и из сети ( RemoteMediator ). Это может привести к тому, что в пользовательском интерфейсе на короткое время появится индикатор загрузки, даже если кэшированные данные доступны немедленно.

Для точного управления, например, для отображения индикатора загрузки только тогда, когда локальная база данных пуста и активна сетевая синхронизация, обращайтесь к свойствам source и mediator непосредственно в вашем составном объекте.

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

Реагировать на изменения состояния загрузки

Возможно, вам потребуется запускать разовые побочные эффекты в зависимости от изменений состояния загрузки, например, прокрутку списка вверх или отображение Snackbar после завершения обновления.

Используйте snapshotFlow внутри LaunchedEffect , чтобы отслеживать изменения состояния в виде потока. Это позволяет применять стандартные операторы Flow , такие как filter и distinctUntilChanged для изоляции конкретных событий.

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

Дополнительные ресурсы

Для получения дополнительной информации о библиотеке страничной навигации и состояниях загрузки обратитесь к следующим ресурсам.

Документация

Просмотры контента

{% verbatim %} {% endverbatim %} {% verbatim %} {% endverbatim %}