Gestire e presentare gli stati di caricamento

La libreria Paging tiene traccia dello stato delle richieste di caricamento per i dati paginati e lo espone tramite la classe LoadState.

Per ogni LoadType e tipo di origine dati (PagingSource o RemoteMediator) viene fornito un segnale LoadState separato. L'oggetto CombinedLoadStates fornito dal listener fornisce informazioni sullo stato di caricamento di tutti questi segnali. Puoi utilizzare queste informazioni dettagliate per mostrare agli utenti gli indicatori di caricamento appropriati.

Caricamento stati in corso…

La libreria Paging espone lo stato di caricamento per l'utilizzo nell'interfaccia utente tramite l'oggetto LoadState. Gli oggetti LoadState assumono una delle tre forme a seconda dello stato di caricamento corrente:

  • Se non è presente alcuna operazione di caricamento attiva e nessun errore, LoadState è un oggetto LoadState.NotLoading. Questa sottoclasse include anche la proprietà endOfPaginationReached, che indica se è stata raggiunta la fine della paginazione.
  • Se è presente un'operazione di caricamento attiva, LoadState è un oggetto LoadState.Loading.
  • Se si verifica un errore, LoadState è un oggetto LoadState.Error.

Accedi a questi stati tramite la proprietà loadState del wrapper LazyPagingItems. Puoi utilizzare questo stato in due modi: gestendo la visibilità dei contenuti principali (ad esempio un indicatore di aggiornamento a schermo intero) o inserendo elementi di caricamento direttamente nel tuo stream LazyColumn (ad esempio un indicatore a piè di pagina).

Accedere allo stato di caricamento con un listener

Per monitorare lo stato di caricamento nell'interfaccia utente, utilizza la proprietà loadState fornita dal wrapper LazyPagingItems. Restituisce un oggetto CombinedLoadStates che consente di reagire al comportamento di caricamento per eventi di aggiornamento, aggiunta o anteposizione.

Nell'esempio seguente, la UI mostra una rotellina di caricamento o un messaggio di errore a seconda dello stato attuale del caricamento dell'aggiornamento (iniziale):

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

Per maggiori informazioni su LazyPagingItems, consulta Set di dati di grandi dimensioni (impaginazione).

Per visualizzare gli indicatori di caricamento all'inizio o alla fine dell'elenco (che fungono da intestazioni o piè di pagina), aggiungi blocchi di elementi dedicati specificamente a questi stati all'interno dell'ambito LazyColumn.

Puoi monitorare lo stato di pre-inserimento per l'intestazione e lo stato di accodamento per il piè di pagina utilizzando l'oggetto CombinedLoadStates.

Nell'esempio seguente, l'elenco mostra una barra di avanzamento o un pulsante Riprova nella parte inferiore dell'elenco quando vengono recuperati altri dati:

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

Accedere a informazioni aggiuntive sullo stato di caricamento

Come mostrato negli esempi precedenti, chiamare pagingItems.loadState.refresh è comodo. Tuttavia, offusca la differenza tra il caricamento dal database locale (PagingSource) e dalla rete (RemoteMediator). Ciò può causare la visualizzazione temporanea di un indicatore di caricamento nell'interfaccia utente anche quando i dati memorizzati nella cache sono immediatamente disponibili.

Per un controllo preciso, ad esempio per mostrare un indicatore di caricamento solo quando il database locale è vuoto e la sincronizzazione di rete è attiva, accedi alle proprietà source e mediator direttamente all'interno del 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()
    }
}

Reagire alle modifiche dello stato di caricamento

Potresti dover attivare effetti collaterali una tantum in base alle modifiche dello stato di caricamento, ad esempio scorrere fino all'inizio di un elenco o mostrare un Snackbar al termine di un aggiornamento.

Utilizza snapshotFlow all'interno di un LaunchedEffect per osservare le modifiche dello stato come stream. In questo modo puoi applicare operatori Flow standard come filter e distinctUntilChanged per isolare eventi specifici.

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

Risorse aggiuntive

Per ulteriori informazioni sulla libreria Paging e sugli stati di caricamento, consulta le seguenti risorse.

Documentazione

Visualizza contenuti