Seitendaten laden und anzeigen

Die Paging-Bibliothek bietet leistungsstarke Funktionen zum Laden und Anzeigen aus einem größeren Dataset besteht. In diesem Leitfaden wird die Verwendung der Paging-Funktion zur Einrichtung eines Streams von Seitendaten aus einer Netzwerkdatenquelle und in einem RecyclerView.

Datenquelle definieren

Der erste Schritt besteht darin, PagingSource-Implementierung um die Datenquelle zu identifizieren. Die API-Klasse PagingSource enthält Folgendes: load() , die Sie überschreiben, um anzugeben, wie Seitendaten aus der in die entsprechende Datenquelle ein.

Verwenden Sie die PagingSource-Klasse direkt, um Kotlin-Koroutinen für asynchrone wird geladen. Die Paging-Bibliothek bietet auch Klassen zur Unterstützung anderer asynchroner Frameworks:

Schlüssel- und Werttypen auswählen

PagingSource<Key, Value> hat zwei Typparameter: Key und Value. Der Schlüssel definiert die Kennung, die zum Laden der Daten verwendet wird, und der Wert ist der Typ des Daten selbst. Wenn Sie beispielsweise Seiten mit User-Objekten aus dem Netzwerk laden indem Sie Int Seitennummern an Rückblick Wählen Sie Int als Typ Key und User als Typ Value aus.

PagingSource definieren

Im folgenden Beispiel wird ein PagingSource wird geladen Seiten von Elementen nach Seitenzahl sortiert. Der Typ Key ist Int und der Typ Value ist User.

Kotlin

class ExamplePagingSource(
    val backend: ExampleBackendService,
    val query: String
) : PagingSource<Int, User>() {
  override suspend fun load(
    params: LoadParams<Int>
  ): LoadResult<Int, User> {
    try {
      // Start refresh at page 1 if undefined.
      val nextPageNumber = params.key ?: 1
      val response = backend.searchUsers(query, nextPageNumber)
      return LoadResult.Page(
        data = response.users,
        prevKey = null, // Only paging forward.
        nextKey = response.nextPageNumber
      )
    } catch (e: Exception) {
      // Handle errors in this block and return LoadResult.Error for
      // expected errors (such as a network failure).
    }
  }

  override fun getRefreshKey(state: PagingState<Int, User>): Int? {
    // Try to find the page key of the closest page to anchorPosition from
    // either the prevKey or the nextKey; you need to handle nullability
    // here.
    //  * prevKey == null -> anchorPage is the first page.
    //  * nextKey == null -> anchorPage is the last page.
    //  * both prevKey and nextKey are null -> anchorPage is the
    //    initial page, so return null.
    return state.anchorPosition?.let { anchorPosition ->
      val anchorPage = state.closestPageToPosition(anchorPosition)
      anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
    }
  }
}

Java

class ExamplePagingSource extends RxPagingSource<Integer, User> {
  @NonNull
  private ExampleBackendService mBackend;
  @NonNull
  private String mQuery;

  ExamplePagingSource(@NonNull ExampleBackendService backend,
    @NonNull String query) {
    mBackend = backend;
    mQuery = query;
  }

  @NotNull
  @Override
  public Single<LoadResult<Integer, User>> loadSingle(
    @NotNull LoadParams<Integer> params) {
    // Start refresh at page 1 if undefined.
    Integer nextPageNumber = params.getKey();
    if (nextPageNumber == null) {
      nextPageNumber = 1;
    }

    return mBackend.searchUsers(mQuery, nextPageNumber)
      .subscribeOn(Schedulers.io())
      .map(this::toLoadResult)
      .onErrorReturn(LoadResult.Error::new);
  }

  private LoadResult<Integer, User> toLoadResult(
    @NonNull SearchUserResponse response) {
    return new LoadResult.Page<>(
      response.getUsers(),
      null, // Only paging forward.
      response.getNextPageNumber(),
      LoadResult.Page.COUNT_UNDEFINED,
      LoadResult.Page.COUNT_UNDEFINED);
  }

  @Nullable
  @Override
  public Integer getRefreshKey(@NotNull PagingState<Integer, User> state) {
    // Try to find the page key of the closest page to anchorPosition from
    // either the prevKey or the nextKey; you need to handle nullability
    // here.
    //  * prevKey == null -> anchorPage is the first page.
    //  * nextKey == null -> anchorPage is the last page.
    //  * both prevKey and nextKey are null -> anchorPage is the
    //    initial page, so return null.
    Integer anchorPosition = state.getAnchorPosition();
    if (anchorPosition == null) {
      return null;
    }

    LoadResult.Page<Integer, User> anchorPage = state.closestPageToPosition(anchorPosition);
    if (anchorPage == null) {
      return null;
    }

    Integer prevKey = anchorPage.getPrevKey();
    if (prevKey != null) {
      return prevKey + 1;
    }

    Integer nextKey = anchorPage.getNextKey();
    if (nextKey != null) {
      return nextKey - 1;
    }

    return null;
  }
}

Java

class ExamplePagingSource extends ListenableFuturePagingSource<Integer, User> {
  @NonNull
  private ExampleBackendService mBackend;
  @NonNull
  private String mQuery;
  @NonNull
  private Executor mBgExecutor;

  ExamplePagingSource(
    @NonNull ExampleBackendService backend,
    @NonNull String query, @NonNull Executor bgExecutor) {
    mBackend = backend;
    mQuery = query;
    mBgExecutor = bgExecutor;
  }

  @NotNull
  @Override
  public ListenableFuture<LoadResult<Integer, User>> loadFuture(@NotNull LoadParams<Integer> params) {
    // Start refresh at page 1 if undefined.
    Integer nextPageNumber = params.getKey();
    if (nextPageNumber == null) {
      nextPageNumber = 1;
    }

    ListenableFuture<LoadResult<Integer, User>> pageFuture =
      Futures.transform(mBackend.searchUsers(mQuery, nextPageNumber),
      this::toLoadResult, mBgExecutor);

    ListenableFuture<LoadResult<Integer, User>> partialLoadResultFuture =
      Futures.catching(pageFuture, HttpException.class,
      LoadResult.Error::new, mBgExecutor);

    return Futures.catching(partialLoadResultFuture,
      IOException.class, LoadResult.Error::new, mBgExecutor);
  }

  private LoadResult<Integer, User> toLoadResult(@NonNull SearchUserResponse response) {
    return new LoadResult.Page<>(response.getUsers(),
    null, // Only paging forward.
    response.getNextPageNumber(),
    LoadResult.Page.COUNT_UNDEFINED,
    LoadResult.Page.COUNT_UNDEFINED);
  }

  @Nullable
  @Override
  public Integer getRefreshKey(@NotNull PagingState<Integer, User> state) {
    // Try to find the page key of the closest page to anchorPosition from
    // either the prevKey or the nextKey; you need to handle nullability
    // here.
    //  * prevKey == null -> anchorPage is the first page.
    //  * nextKey == null -> anchorPage is the last page.
    //  * both prevKey and nextKey are null -> anchorPage is the
    //    initial page, so return null.
    Integer anchorPosition = state.getAnchorPosition();
    if (anchorPosition == null) {
      return null;
    }

    LoadResult.Page<Integer, User> anchorPage = state.closestPageToPosition(anchorPosition);
    if (anchorPage == null) {
      return null;
    }

    Integer prevKey = anchorPage.getPrevKey();
    if (prevKey != null) {
      return prevKey + 1;
    }

    Integer nextKey = anchorPage.getNextKey();
    if (nextKey != null) {
      return nextKey - 1;
    }

    return null;
  }
}

Eine typische PagingSource-Implementierung übergibt die in ihrer -Konstruktor an die Methode load(), um die entsprechenden Daten für eine Abfrage zu laden. Im im Beispiel oben sind diese Parameter:

  • backend: eine Instanz des Back-End-Dienstes, der die Daten bereitstellt
  • query: Suchanfrage, die an den in backend angegebenen Dienst gesendet werden soll

Die LoadParams -Objekt enthält Informationen zum auszuführenden Ladevorgang. Dieses enthält den zu ladenden Schlüssel und die Anzahl der zu ladenden Elemente.

Die LoadResult -Objekt enthält das Ergebnis des Ladevorgangs. LoadResult ist eine abgeschlossene Klasse Es gibt zwei Möglichkeiten, je nachdem, ob der load()-Aufruf erfolgreich war:

  • Wenn der Ladevorgang erfolgreich ist, wird ein LoadResult.Page-Objekt zurückgegeben.
  • Wenn das Laden nicht erfolgreich ist, wird ein LoadResult.Error-Objekt zurückgegeben.

Die folgende Abbildung zeigt, wie die Funktion load() in diesem Beispiel funktioniert. empfängt den Schlüssel für jeden Ladevorgang und stellt den Schlüssel für den nachfolgenden Ladevorgang bereit.

<ph type="x-smartling-placeholder">
</ph> Bei jedem &quot;load()&quot;-Aufruf übernimmt die ExamplePagingSource den aktuellen Schlüssel
    und gibt den nächsten zu ladenden Schlüssel zurück. <ph type="x-smartling-placeholder">
</ph> Abbildung 1: Diagramm, das zeigt, wie load() den Schlüssel verwendet und aktualisiert.

Durch die PagingSource-Implementierung muss auch ein getRefreshKey() mit einer PagingState-Objekt als . Sie gibt den Schlüssel zurück, der an die Methode load() übergeben werden soll, wenn die Daten nach dem anfänglichen Ladevorgang aktualisiert oder ungültig gemacht werden. In der Paging Library wird dies automatisch bei nachfolgenden Aktualisierungen der Daten.

Fehler verarbeiten

Anfragen zum Laden von Daten können aus verschiedenen Gründen fehlschlagen, insbesondere beim Laden Netzwerk übertragen werden. Berichtfehler, die beim Laden aufgetreten sind, durch Rückgabe des Fehlers LoadResult.Error-Objekt aus der Methode load().

Sie können beispielsweise Ladefehler in ExamplePagingSource erkennen und melden. aus dem vorherigen Beispiel erhalten, indem Sie der Methode load() Folgendes hinzufügen:

Kotlin

catch (e: IOException) {
  // IOException for network failures.
  return LoadResult.Error(e)
} catch (e: HttpException) {
  // HttpException for any non-2xx HTTP status codes.
  return LoadResult.Error(e)
}

Java

return backend.searchUsers(searchTerm, nextPageNumber)
  .subscribeOn(Schedulers.io())
  .map(this::toLoadResult)
  .onErrorReturn(LoadResult.Error::new);

Java

ListenableFuture<LoadResult<Integer, User>> pageFuture = Futures.transform(
  backend.searchUsers(query, nextPageNumber), this::toLoadResult,
  bgExecutor);

ListenableFuture<LoadResult<Integer, User>> partialLoadResultFuture = Futures.catching(
  pageFuture, HttpException.class, LoadResult.Error::new,
  bgExecutor);

return Futures.catching(partialLoadResultFuture,
  IOException.class, LoadResult.Error::new, bgExecutor);

Weitere Informationen zum Umgang mit Retrofit-Fehlern finden Sie in den Beispielen in der PagingSource API-Referenz

PagingSource erfasst LoadResult.Error Objekte und sendet sie an die UI, um darauf reagieren zu können. Weitere Informationen zum Freigeben des Ladestatus finden Sie unter Verwalten und Präsentieren von Ladevorgängen Bundesländer.

PagingData-Stream einrichten

Als Nächstes benötigen Sie einen Stream von Page-Daten aus der PagingSource-Implementierung. Richten Sie den Datenstream in Ihrem ViewModel ein. Die Die Klasse Pager bietet Methoden, einen reaktiven Strom von PagingData-Objekte aus einem PagingSource Die Paging-Bibliothek unterstützt verschiedene Streamtypen, einschließlich Flow, LiveData und der Typen Flowable und Observable von RxJava.

Wenn Sie eine Pager-Instanz zum Einrichten des reaktiven Streams erstellen, müssen Sie stellen Sie der Instanz eine PagingConfig-Konfiguration und eine Funktion, die Pager mitteilt, wie eine Instanz Ihres PagingSource-Implementierung:

Kotlin

val flow = Pager(
  // Configure how data is loaded by passing additional properties to
  // PagingConfig, such as prefetchDistance.
  PagingConfig(pageSize = 20)
) {
  ExamplePagingSource(backend, query)
}.flow
  .cachedIn(viewModelScope)

Java

// CoroutineScope helper provided by the lifecycle-viewmodel-ktx artifact.
CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(viewModel);
Pager<Integer, User> pager = Pager<>(
  new PagingConfig(/* pageSize = */ 20),
  () -> ExamplePagingSource(backend, query));

Flowable<PagingData<User>> flowable = PagingRx.getFlowable(pager);
PagingRx.cachedIn(flowable, viewModelScope);

Java

// CoroutineScope helper provided by the lifecycle-viewmodel-ktx artifact.
CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(viewModel);
Pager<Integer, User> pager = Pager<>(
  new PagingConfig(/* pageSize = */ 20),
  () -> ExamplePagingSource(backend, query));

PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), viewModelScope);

Der Operator cachedIn() sorgt dafür, dass der Datenstream geteilt werden kann, und speichert die geladenen Daten mit der angegebenen CoroutineScope. In diesem Beispiel wird die Methode viewModelScope verwendet. vom Lebenszyklus-lifecycle-viewmodel-ktx-Artefakt bereitgestellt.

Das Pager-Objekt ruft die load()-Methode aus dem PagingSource-Objekt auf. und es die Möglichkeit gibt, LoadParams-Objekt und den Erhalt der LoadResult Objekt im Gegenzug erhalten.

RecyclerView-Adapter definieren

Du musst auch einen Adapter einrichten, um die Daten an dein RecyclerView zu senden Liste. Die Paging-Bibliothek stellt hierfür die Klasse PagingDataAdapter bereit. zu verstehen.

Definieren Sie eine Klasse, die PagingDataAdapter erweitert. In diesem Beispiel UserAdapter erweitert PagingDataAdapter, um ein RecyclerView bereitzustellen Adapter für Listenelemente vom Typ User und die Verwendung von UserViewHolder als Ansicht Inhaber:

Kotlin

class UserAdapter(diffCallback: DiffUtil.ItemCallback<User>) :
  PagingDataAdapter<User, UserViewHolder>(diffCallback) {
  override fun onCreateViewHolder(
    parent: ViewGroup,
    viewType: Int
  ): UserViewHolder {
    return UserViewHolder(parent)
  }

  override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
    val item = getItem(position)
    // Note that item can be null. ViewHolder must support binding a
    // null item as a placeholder.
    holder.bind(item)
  }
}

Java

class UserAdapter extends PagingDataAdapter<User, UserViewHolder> {
  UserAdapter(@NotNull DiffUtil.ItemCallback<User> diffCallback) {
    super(diffCallback);
  }

  @NonNull
  @Override
  public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    return new UserViewHolder(parent);
  }

  @Override
  public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
    User item = getItem(position);
    // Note that item can be null. ViewHolder must support binding a
    // null item as a placeholder.
    holder.bind(item);
  }
}

Java

class UserAdapter extends PagingDataAdapter<User, UserViewHolder> {
  UserAdapter(@NotNull DiffUtil.ItemCallback<User> diffCallback) {
    super(diffCallback);
  }

  @NonNull
  @Override
  public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    return new UserViewHolder(parent);
  }

  @Override
  public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
    User item = getItem(position);
    // Note that item can be null. ViewHolder must support binding a
    // null item as a placeholder.
    holder.bind(item);
  }
}

Ihr Adapter muss auch die onCreateViewHolder() und onBindViewHolder()-Methoden und geben Sie einen DiffUtil.ItemCallback Das funktioniert genauso wie beim Definieren der RecyclerView-Liste Adapter:

Kotlin

object UserComparator : DiffUtil.ItemCallback<User>() {
  override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
    // Id is unique.
    return oldItem.id == newItem.id
  }

  override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
    return oldItem == newItem
  }
}

Java

class UserComparator extends DiffUtil.ItemCallback<User> {
  @Override
  public boolean areItemsTheSame(@NonNull User oldItem,
    @NonNull User newItem) {
    // Id is unique.
    return oldItem.id.equals(newItem.id);
  }

  @Override
  public boolean areContentsTheSame(@NonNull User oldItem,
    @NonNull User newItem) {
    return oldItem.equals(newItem);
  }
}

Java

class UserComparator extends DiffUtil.ItemCallback<User> {
  @Override
  public boolean areItemsTheSame(@NonNull User oldItem,
    @NonNull User newItem) {
    // Id is unique.
    return oldItem.id.equals(newItem.id);
  }

  @Override
  public boolean areContentsTheSame(@NonNull User oldItem,
    @NonNull User newItem) {
    return oldItem.equals(newItem);
  }
}

Daten aus Seiten in Ihrer Benutzeroberfläche anzeigen

Nachdem Sie nun eine PagingSource definiert haben, haben Sie eine Möglichkeit erstellt, wie Ihre App einen Stream von PagingData generiert und eine PagingDataAdapter definiert hat, Elemente miteinander verbinden und Daten aus Seiten in Ihrem Aktivitäten.

Führe die folgenden Schritte im onCreate oder Fragment deiner Aktivität aus onViewCreated-Methode:

  1. Erstellen Sie eine Instanz Ihrer PagingDataAdapter-Klasse.
  2. Übergeben Sie die PagingDataAdapter-Instanz an den RecyclerView in der Sie Ihre Seitendaten anzeigen möchten.
  3. Beobachten Sie den PagingData-Stream und übergeben Sie jeden generierten Wert an Ihren submitData()-Methode des Adapters.

Kotlin

val viewModel by viewModels<ExampleViewModel>()

val pagingAdapter = UserAdapter(UserComparator)
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = pagingAdapter

// Activities can use lifecycleScope directly; fragments use
// viewLifecycleOwner.lifecycleScope.
lifecycleScope.launch {
  viewModel.flow.collectLatest { pagingData ->
    pagingAdapter.submitData(pagingData)
  }
}

Java

ExampleViewModel viewModel = new ViewModelProvider(this)
  .get(ExampleViewModel.class);

UserAdapter pagingAdapter = new UserAdapter(new UserComparator());
RecyclerView recyclerView = findViewById<RecyclerView>(
  R.id.recycler_view);
recyclerView.adapter = pagingAdapter

viewModel.flowable
  // Using AutoDispose to handle subscription lifecycle.
  // See: https://github.com/uber/AutoDispose.
  .to(autoDisposable(AndroidLifecycleScopeProvider.from(this)))
  .subscribe(pagingData -> pagingAdapter.submitData(lifecycle, pagingData));

Java

ExampleViewModel viewModel = new ViewModelProvider(this)
  .get(ExampleViewModel.class);

UserAdapter pagingAdapter = new UserAdapter(new UserComparator());
RecyclerView recyclerView = findViewById<RecyclerView>(
  R.id.recycler_view);
recyclerView.adapter = pagingAdapter

// Activities can use getLifecycle() directly; fragments use
// getViewLifecycleOwner().getLifecycle().
viewModel.liveData.observe(this, pagingData ->
  pagingAdapter.submitData(getLifecycle(), pagingData));

In der Liste „RecyclerView“ werden jetzt die Seitendaten aus der Datenquelle und lädt bei Bedarf automatisch eine andere Seite.

Weitere Informationen

Weitere Informationen zur Paging-Bibliothek finden Sie in den folgenden zusätzlichen Ressourcen:

Codelabs

Produktproben