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:
- Um RxJava zu verwenden, implementieren Sie
RxPagingSource
. - Um
ListenableFuture
von Guava zu verwenden, implementieren SieListenableFuturePagingSource
.
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 bereitstelltquery
: Suchanfrage, die an den inbackend
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.
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:
- Erstellen Sie eine Instanz Ihrer
PagingDataAdapter
-Klasse. - Übergeben Sie die
PagingDataAdapter
-Instanz an denRecyclerView
in der Sie Ihre Seitendaten anzeigen möchten. - Beobachten Sie den
PagingData
-Stream und übergeben Sie jeden generierten Wert an IhrensubmitData()
-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
- Seitenumbruch für Android-Architekturkomponenten Beispiel
- Seitenumbruch mit Netzwerkverbindung für Komponenten der Android-Architektur Beispiel
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Seite aus Netzwerk und Datenbank
- Zu Paging 3 migrieren
- Paging Library