Gérer et présenter les états de chargement

La bibliothèque Paging suit l'état des requêtes de chargement pour les données paginées et les expose via la classe LoadState.

Un signal LoadState distinct est fourni pour chaque LoadType et type de source de données (soit PagingSource, soit RemoteMediator). L'objet CombinedLoadStates fourni par l'écouteur apporte des informations sur l'état de chargement de tous ces signaux. Vous pouvez utiliser ces informations détaillées pour afficher les indicateurs de chargement appropriés pour vos utilisateurs.

États de chargement

La bibliothèque Paging expose l'état de chargement en vue de son utilisation dans l'interface utilisateur via l'objet LoadState. Les objets LoadState peuvent prendre l'une des trois formes suivantes selon l'état de chargement actuel :

  • S'il n'y a pas d'opération de chargement active ni d'erreur, LoadState est un objet LoadState.NotLoading. Cette sous-classe inclut également la propriété endOfPaginationReached, qui indique si la fin de la pagination a été atteinte.
  • Si une opération de chargement est active, LoadState est un objet LoadState.Loading.
  • En cas d'erreur, LoadState est un objet LoadState.Error.

Accédez à ces états via la propriété loadState de votre wrapper LazyPagingItems. Vous pouvez utiliser cet état de deux manières : en gérant la visibilité du contenu principal (comme un indicateur de chargement en plein écran) ou en insérant des éléments de chargement directement dans votre flux LazyColumn (comme un indicateur de chargement de pied de page).

Accéder à l'état de chargement avec un écouteur

Pour surveiller l'état de chargement dans votre UI, utilisez la propriété loadState fournie par le wrapper LazyPagingItems. Cela renvoie un objet CombinedLoadStates qui vous permet de réagir au comportement de chargement pour les événements d'actualisation, d'ajout ou de préfixation.

Dans l'exemple suivant, l'UI affiche une icône de chargement ou un message d'erreur en fonction de l'état actuel du chargement de l'actualisation (initial) :

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

Pour en savoir plus sur LazyPagingItems, consultez Ensembles de données volumineux (pagination).

Pour afficher des indicateurs de chargement au début ou à la fin de votre liste (en guise d'en-têtes ou de pieds de page), ajoutez des blocs d'éléments dédiés spécifiquement à ces états dans votre portée LazyColumn.

Vous pouvez surveiller l'état de préfixe de l'en-tête et l'état de suffixe du pied de page à l'aide de l'objet CombinedLoadStates.

Dans l'exemple suivant, la liste affiche une barre de progression ou un bouton "Retry" (Réessayer) en bas de la liste lorsque d'autres données sont en cours de récupération :

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

Accéder à des informations supplémentaires sur l'état de chargement

Comme indiqué dans les exemples précédents, l'appel de pagingItems.loadState.refresh est pratique. Toutefois, cela masque la différence entre le chargement à partir de votre base de données locale (PagingSource) et votre réseau (RemoteMediator). Cela peut entraîner l'affichage bref d'une icône de chargement dans l'UI, même lorsque les données mises en cache sont immédiatement disponibles.

Pour un contrôle précis, comme l'affichage d'un indicateur de chargement uniquement lorsque la base de données locale est vide et qu'une synchronisation réseau est active, accédez directement aux propriétés source et mediator dans votre composable.

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

Réagir aux changements d'état de chargement

Vous devrez peut-être déclencher des effets secondaires ponctuels en fonction des changements d'état de chargement, comme faire défiler une liste jusqu'en haut ou afficher un Snackbar lorsqu'une actualisation est terminée.

Utilisez snapshotFlow dans un LaunchedEffect pour observer les changements d'état sous forme de flux. Cela vous permet d'appliquer des opérateurs Flow standards tels que filter et distinctUntilChanged pour isoler des événements spécifiques.

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

Ressources supplémentaires

Pour en savoir plus sur la bibliothèque Paging et les états de chargement, consultez les ressources suivantes.

Documentation

Afficher le contenu