Ladestatus verwalten und einblenden

Die Paging-Bibliothek verfolgt den Status von Ladeanfragen für ausgelagerte Daten und stellt sie über die Klasse LoadState bereit. Ihre App kann einen Listener mit dem PagingDataAdapter registrieren, um Informationen zum aktuellen Status zu erhalten und die UI entsprechend zu aktualisieren. Diese Status werden vom Adapter bereitgestellt, da sie mit der UI synchron sind. Das bedeutet, dass der Listener Aktualisierungen erhält, wenn der Seitenaufbau auf die UI angewendet wurde.

Für jeden LoadType- und Datenquellentyp (entweder PagingSource oder RemoteMediator) wird ein separates LoadState-Signal bereitgestellt. Das vom Listener bereitgestellte Objekt CombinedLoadStates liefert Informationen zum Ladestatus aller dieser Signale. Anhand dieser detaillierten Informationen kannst du deinen Nutzern die entsprechenden Ladeindikatoren anzeigen.

Status werden geladen

Die Paginierungsbibliothek zeigt den Ladestatus zur Verwendung in der UI über das LoadState-Objekt an. LoadState-Objekte nehmen je nach aktuellem Ladestatus eine von drei Formen an:

  • Wenn kein aktiver Ladevorgang und kein Fehler vorhanden ist, ist LoadState ein LoadState.NotLoading-Objekt. Diese abgeleitete Klasse enthält auch die Property endOfPaginationReached, die angibt, ob das Ende der Paginierung erreicht wurde.
  • Bei einem aktiven Ladevorgang ist LoadState ein LoadState.Loading-Objekt.
  • Bei einem Fehler ist LoadState ein LoadState.Error-Objekt.

Es gibt zwei Möglichkeiten, LoadState in Ihrer UI zu verwenden: Sie können entweder einen Listener verwenden oder einen speziellen Listenadapter, um den Ladestatus direkt in der Liste RecyclerView darzustellen.

Mit einem Listener auf den Ladestatus zugreifen

Um den Ladestatus für die allgemeine Verwendung in Ihrer UI abzurufen, verwenden Sie den Stream loadStateFlow oder die Methode addLoadStateListener(), die von Ihrer PagingDataAdapter bereitgestellt wird. Diese Mechanismen bieten Zugriff auf ein CombinedLoadStates-Objekt, das Informationen zum LoadState-Verhalten für jeden Ladetyp enthält.

Im folgenden Beispiel werden im PagingDataAdapter abhängig vom aktuellen Status des Aktualisierungsladevorgangs verschiedene UI-Komponenten angezeigt:

Kotlin

// Activities can use lifecycleScope directly, but Fragments should instead use
// viewLifecycleOwner.lifecycleScope.
lifecycleScope.launch {
  pagingAdapter.loadStateFlow.collectLatest { loadStates ->
    progressBar.isVisible = loadStates.refresh is LoadState.Loading
    retry.isVisible = loadState.refresh !is LoadState.Loading
    errorMsg.isVisible = loadState.refresh is LoadState.Error
  }
}

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  progressBar.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.VISIBLE : View.GONE);
  retry.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.GONE : View.VISIBLE);
  errorMsg.setVisibility(loadStates.refresh instanceof LoadState.Error
    ? View.VISIBLE : View.GONE);
});

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  progressBar.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.VISIBLE : View.GONE);
  retry.setVisibility(loadStates.refresh instanceof LoadState.Loading
    ? View.GONE : View.VISIBLE);
  errorMsg.setVisibility(loadStates.refresh instanceof LoadState.Error
    ? View.VISIBLE : View.GONE);
});

Weitere Informationen zu CombinedLoadStates finden Sie unter Auf zusätzliche Informationen zum Ladestatus zugreifen.

Ladestatus mit einem Adapter darstellen

Die Paging-Bibliothek bietet einen weiteren Listenadapter mit dem Namen LoadStateAdapter, mit dem der Ladestatus direkt in der angezeigten Liste der ausgelagerten Daten dargestellt wird. Dieser Adapter bietet Zugriff auf den aktuellen Ladestatus der Liste, den Sie an einen Halter einer benutzerdefinierten Ansicht übergeben können, der die Informationen anzeigt.

Erstellen Sie zuerst eine Ansichtsinhaberklasse, die Verweise auf die Lade- und Fehleransichten auf Ihrem Bildschirm beibehält. Erstellen Sie eine bind()-Funktion, die einen LoadState als Parameter akzeptiert. Diese Funktion sollte die Sichtbarkeit der Ansicht basierend auf dem Parameter für den Ladestatus ein- und ausschalten:

Kotlin

class LoadStateViewHolder(
  parent: ViewGroup,
  retry: () -> Unit
) : RecyclerView.ViewHolder(
  LayoutInflater.from(parent.context)
    .inflate(R.layout.load_state_item, parent, false)
) {
  private val binding = LoadStateItemBinding.bind(itemView)
  private val progressBar: ProgressBar = binding.progressBar
  private val errorMsg: TextView = binding.errorMsg
  private val retry: Button = binding.retryButton
    .also {
      it.setOnClickListener { retry() }
    }

  fun bind(loadState: LoadState) {
    if (loadState is LoadState.Error) {
      errorMsg.text = loadState.error.localizedMessage
    }

    progressBar.isVisible = loadState is LoadState.Loading
    retry.isVisible = loadState is LoadState.Error
    errorMsg.isVisible = loadState is LoadState.Error
  }
}

Java

class LoadStateViewHolder extends RecyclerView.ViewHolder {
  private ProgressBar mProgressBar;
  private TextView mErrorMsg;
  private Button mRetry;

  LoadStateViewHolder(
    @NonNull ViewGroup parent,
    @NonNull View.OnClickListener retryCallback) {
    super(LayoutInflater.from(parent.getContext())
      .inflate(R.layout.load_state_item, parent, false));

    LoadStateItemBinding binding = LoadStateItemBinding.bind(itemView);
    mProgressBar = binding.progressBar;
    mErrorMsg = binding.errorMsg;
    mRetry = binding.retryButton;
  }

  public void bind(LoadState loadState) {
    if (loadState instanceof LoadState.Error) {
      LoadState.Error loadStateError = (LoadState.Error) loadState;
      mErrorMsg.setText(loadStateError.getError().getLocalizedMessage());
    }
    mProgressBar.setVisibility(loadState instanceof LoadState.Loading
      ? View.VISIBLE : View.GONE);
    mRetry.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
    mErrorMsg.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
  }
}

Java

class LoadStateViewHolder extends RecyclerView.ViewHolder {
  private ProgressBar mProgressBar;
  private TextView mErrorMsg;
  private Button mRetry;

  LoadStateViewHolder(
    @NonNull ViewGroup parent,
    @NonNull View.OnClickListener retryCallback) {
    super(LayoutInflater.from(parent.getContext())
      .inflate(R.layout.load_state_item, parent, false));

    LoadStateItemBinding binding = LoadStateItemBinding.bind(itemView);
    mProgressBar = binding.progressBar;
    mErrorMsg = binding.errorMsg;
    mRetry = binding.retryButton;
  }

  public void bind(LoadState loadState) {
    if (loadState instanceof LoadState.Error) {
      LoadState.Error loadStateError = (LoadState.Error) loadState;
      mErrorMsg.setText(loadStateError.getError().getLocalizedMessage());
    }
    mProgressBar.setVisibility(loadState instanceof LoadState.Loading
      ? View.VISIBLE : View.GONE);
    mRetry.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
    mErrorMsg.setVisibility(loadState instanceof LoadState.Error
      ? View.VISIBLE : View.GONE);
  }
}

Erstellen Sie als Nächstes eine Klasse, die LoadStateAdapter implementiert, und definieren Sie die Methoden onCreateViewHolder() und onBindViewHolder(). Diese Methoden erstellen eine Instanz Ihres benutzerdefinierten Ansichtsinhabers und binden den zugehörigen Ladestatus.

Kotlin

// Adapter that displays a loading spinner when
// state is LoadState.Loading, and an error message and retry
// button when state is LoadState.Error.
class ExampleLoadStateAdapter(
  private val retry: () -> Unit
) : LoadStateAdapter<LoadStateViewHolder>() {

  override fun onCreateViewHolder(
    parent: ViewGroup,
    loadState: LoadState
  ) = LoadStateViewHolder(parent, retry)

  override fun onBindViewHolder(
    holder: LoadStateViewHolder,
    loadState: LoadState
  ) = holder.bind(loadState)
}

Java

// Adapter that displays a loading spinner when
// state is LoadState.Loading, and an error message and retry
// button when state is LoadState.Error.
class ExampleLoadStateAdapter extends LoadStateAdapter<LoadStateViewHolder> {
  private View.OnClickListener mRetryCallback;

  ExampleLoadStateAdapter(View.OnClickListener retryCallback) {
    mRetryCallback = retryCallback;
  }

  @NotNull
  @Override
  public LoadStateViewHolder onCreateViewHolder(@NotNull ViewGroup parent,
    @NotNull LoadState loadState) {
    return new LoadStateViewHolder(parent, mRetryCallback);
  }

  @Override
  public void onBindViewHolder(@NotNull LoadStateViewHolder holder,
    @NotNull LoadState loadState) {
    holder.bind(loadState);
  }
}

Java

// Adapter that displays a loading spinner when
// state is LoadState.Loading, and an error message and retry
// button when state is LoadState.Error.
class ExampleLoadStateAdapter extends LoadStateAdapter<LoadStateViewHolder> {
  private View.OnClickListener mRetryCallback;

  ExampleLoadStateAdapter(View.OnClickListener retryCallback) {
    mRetryCallback = retryCallback;
  }

  @NotNull
  @Override
  public LoadStateViewHolder onCreateViewHolder(@NotNull ViewGroup parent,
    @NotNull LoadState loadState) {
    return new LoadStateViewHolder(parent, mRetryCallback);
  }

  @Override
  public void onBindViewHolder(@NotNull LoadStateViewHolder holder,
    @NotNull LoadState loadState) {
    holder.bind(loadState);
  }
}

Rufen Sie die Methode withLoadStateHeaderAndFooter() über das PagingDataAdapter-Objekt auf, um den Ladefortschritt in einer Kopf- und Fußzeile anzuzeigen:

Kotlin

pagingAdapter
  .withLoadStateHeaderAndFooter(
    header = ExampleLoadStateAdapter(adapter::retry),
    footer = ExampleLoadStateAdapter(adapter::retry)
  )

Java

pagingAdapter
  .withLoadStateHeaderAndFooter(
    new ExampleLoadStateAdapter(pagingAdapter::retry),
    new ExampleLoadStateAdapter(pagingAdapter::retry));

Java

pagingAdapter
  .withLoadStateHeaderAndFooter(
    new ExampleLoadStateAdapter(pagingAdapter::retry),
    new ExampleLoadStateAdapter(pagingAdapter::retry));

Sie können stattdessen withLoadStateHeader() oder withLoadStateFooter() aufrufen, wenn die RecyclerView-Liste den Ladestatus nur in der Kopfzeile oder nur in der Fußzeile anzeigen soll.

Auf zusätzliche Informationen zum Ladestatus zugreifen

Das CombinedLoadStates-Objekt aus PagingDataAdapter bietet Informationen zu den Ladestatus für Ihre PagingSource-Implementierung und auch für Ihre RemoteMediator-Implementierung, falls vorhanden.

Sie können die Attribute refresh, append und prepend von CombinedLoadStates verwenden, um auf ein LoadState-Objekt für den entsprechenden Ladetyp zuzugreifen. Diese Attribute richten sich in der Regel auf den Ladestatus der Implementierung RemoteMediator, sofern vorhanden. Andernfalls enthalten sie den entsprechenden Ladestatus aus der Implementierung PagingSource. Ausführliche Informationen zur zugrunde liegenden Logik finden Sie in der Referenzdokumentation zu CombinedLoadStates.

Kotlin

lifecycleScope.launch {
  pagingAdapter.loadStateFlow.collectLatest { loadStates ->
    // Observe refresh load state from RemoteMediator if present, or
    // from PagingSource otherwise.
    refreshLoadState: LoadState = loadStates.refresh
    // Observe prepend load state from RemoteMediator if present, or
    // from PagingSource otherwise.
    prependLoadState: LoadState = loadStates.prepend
    // Observe append load state from RemoteMediator if present, or
    // from PagingSource otherwise.
    appendLoadState: LoadState = loadStates.append
  }
}

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  // Observe refresh load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState refreshLoadState = loadStates.refresh;
  // Observe prepend load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState prependLoadState = loadStates.prepend;
  // Observe append load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState appendLoadState = loadStates.append;
});

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  // Observe refresh load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState refreshLoadState = loadStates.refresh;
  // Observe prepend load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState prependLoadState = loadStates.prepend;
  // Observe append load state from RemoteMediator if present, or
  // from PagingSource otherwise.
  LoadState appendLoadState = loadStates.append;
});

Beachten Sie jedoch, dass nur die Ladestatus PagingSource garantiert synchron mit UI-Aktualisierungen sind. Da die Attribute refresh, append und prepend möglicherweise den Ladestatus von PagingSource oder RemoteMediator übernehmen können, ist nicht gewährleistet, dass sie mit UI-Aktualisierungen synchron sind. Dies kann zu UI-Problemen führen, wenn der Ladevorgang abgeschlossen ist, bevor der UI neue Daten hinzugefügt wurden.

Aus diesem Grund funktionieren die praktischen Zugriffsfunktionen gut, um den Ladestatus in einer Kopf- oder Fußzeile anzuzeigen. In anderen Fällen müssen Sie jedoch möglicherweise über PagingSource oder RemoteMediator speziell auf den Ladestatus zugreifen. CombinedLoadStates stellt die Attribute source und mediator für diesen Zweck bereit. Mit diesen Attributen wird jeweils ein LoadStates-Objekt bereitgestellt, das die LoadState-Objekte für PagingSource bzw. RemoteMediator enthält:

Kotlin

lifecycleScope.launch {
  pagingAdapter.loadStateFlow.collectLatest { loadStates ->
    // Directly access the RemoteMediator refresh load state.
    mediatorRefreshLoadState: LoadState? = loadStates.mediator.refresh
    // Directly access the RemoteMediator append load state.
    mediatorAppendLoadState: LoadState? = loadStates.mediator.append
    // Directly access the RemoteMediator prepend load state.
    mediatorPrependLoadState: LoadState? = loadStates.mediator.prepend
    // Directly access the PagingSource refresh load state.
    sourceRefreshLoadState: LoadState = loadStates.source.refresh
    // Directly access the PagingSource append load state.
    sourceAppendLoadState: LoadState = loadStates.source.append
    // Directly access the PagingSource prepend load state.
    sourcePrependLoadState: LoadState = loadStates.source.prepend
  }
}

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  // Directly access the RemoteMediator refresh load state.
  LoadState mediatorRefreshLoadState = loadStates.mediator.refresh;
  // Directly access the RemoteMediator append load state.
  LoadState mediatorAppendLoadState = loadStates.mediator.append;
  // Directly access the RemoteMediator prepend load state.
  LoadState mediatorPrependLoadState = loadStates.mediator.prepend;
  // Directly access the PagingSource refresh load state.
  LoadState sourceRefreshLoadState = loadStates.source.refresh;
  // Directly access the PagingSource append load state.
  LoadState sourceAppendLoadState = loadStates.source.append;
  // Directly access the PagingSource prepend load state.
  LoadState sourcePrependLoadState = loadStates.source.prepend;
});

Java

pagingAdapter.addLoadStateListener(loadStates -> {
  // Directly access the RemoteMediator refresh load state.
  LoadState mediatorRefreshLoadState = loadStates.mediator.refresh;
  // Directly access the RemoteMediator append load state.
  LoadState mediatorAppendLoadState = loadStates.mediator.append;
  // Directly access the RemoteMediator prepend load state.
  LoadState mediatorPrependLoadState = loadStates.mediator.prepend;
  // Directly access the PagingSource refresh load state.
  LoadState sourceRefreshLoadState = loadStates.source.refresh;
  // Directly access the PagingSource append load state.
  LoadState sourceAppendLoadState = loadStates.source.append;
  // Directly access the PagingSource prepend load state.
  LoadState sourcePrependLoadState = loadStates.source.prepend;
});

Kettenoperatoren bei LoadState

Da das CombinedLoadStates-Objekt Zugriff auf alle Änderungen des Ladestatus bietet, ist es wichtig, den Ladestatusstream anhand bestimmter Ereignisse zu filtern. So wird sichergestellt, dass Sie die UI zum richtigen Zeitpunkt aktualisieren, um ruckelige UI-Aktualisierungen und unnötige Aktualisierungen zu vermeiden.

Angenommen, Sie möchten eine leere Ansicht anzeigen, aber erst nach Abschluss des anfänglichen Ladens der Daten. In diesem Anwendungsfall müssen Sie prüfen, ob der Ladevorgang der Datenaktualisierung gestartet wurde, und dann auf den Status NotLoading warten, um zu bestätigen, dass die Aktualisierung abgeschlossen ist. Sie müssen alle Signale herausfiltern, mit Ausnahme der, die Sie benötigen:

Kotlin

lifecycleScope.launchWhenCreated {
  adapter.loadStateFlow
    // Only emit when REFRESH LoadState for RemoteMediator changes.
    .distinctUntilChangedBy { it.refresh }
    // Only react to cases where REFRESH completes, such as NotLoading.
    .filter { it.refresh is LoadState.NotLoading }
    // Scroll to top is synchronous with UI updates, even if remote load was
    // triggered.
    .collect { binding.list.scrollToPosition(0) }
}

Java

PublishSubject<CombinedLoadStates> subject = PublishSubject.create();
Disposable disposable =
  subject.distinctUntilChanged(CombinedLoadStates::getRefresh)
  .filter(
    combinedLoadStates -> combinedLoadStates.getRefresh() instanceof LoadState.NotLoading)
  .subscribe(combinedLoadStates -> binding.list.scrollToPosition(0));

pagingAdapter.addLoadStateListener(loadStates -> {
  subject.onNext(loadStates);
});

Java

LiveData<CombinedLoadStates> liveData = new MutableLiveData<>();
LiveData<LoadState> refreshLiveData =
  Transformations.map(liveData, CombinedLoadStates::getRefresh);
LiveData<LoadState> distinctLiveData =
  Transformations.distinctUntilChanged(refreshLiveData);

distinctLiveData.observeForever(loadState -> {
  if (loadState instanceof LoadState.NotLoading) {
    binding.list.scrollToPosition(0);
  }
});

In diesem Beispiel wird gewartet, bis der Ladestatus der Aktualisierung aktualisiert wurde, wird jedoch nur ausgelöst, wenn der Status NotLoading ist. Dadurch wird sichergestellt, dass die Remote-Aktualisierung vollständig abgeschlossen ist, bevor UI-Aktualisierungen durchgeführt werden.

Stream-APIs ermöglichen diese Art von Vorgang. Ihre Anwendung kann die benötigten Ladeereignisse angeben und die neuen Daten verarbeiten, wenn die entsprechenden Kriterien erfüllt sind.