Die Paging-Bibliothek bietet leistungsstarke Funktionen zum Laden und Anzeigen von seitenbasierten Daten aus einem größeren Dataset. In diesem Leitfaden wird gezeigt, wie Sie mit der Paging-Bibliothek einen Stream von paginierten Daten aus einer Netzwerkdatenquelle einrichten und in einer Lazy List anzeigen.
Datenquelle definieren
Der erste Schritt besteht darin, eine PagingSource-Implementierung zu definieren, um die Datenquelle zu identifizieren. Die API-Klasse PagingSource enthält die Methode load, die Sie überschreiben, um anzugeben, wie paginierte Daten aus der entsprechenden Datenquelle abgerufen werden sollen.
Verwenden Sie die Klasse PagingSource direkt, um Kotlin-Koroutinen für das asynchrone Laden zu verwenden.
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 der Daten selbst. Wenn Sie beispielsweise Seiten mit User-Objekten aus dem Netzwerk laden, indem Sie Int-Seitennummern an Retrofit übergeben, wählen Sie Int als Key-Typ und User als Value-Typ aus.
PagingSource definieren
Im folgenden Beispiel wird eine PagingSource implementiert, die Seiten mit Elementen nach Seitennummer lädt. Der Typ Key ist Int und der Typ Value ist User.
class ExamplePagingSource(
val backend: ExampleBackendService,
val query: String
) : PagingSource<Int, User>() {
override suspend fun load(
params: LoadParams<Int>
): LoadResult<Int, User> {
init {
// the data source is expected to be immutable
// invalidate PagingSource if data source
// has updated
backEnd.addDatabaseOnChangedListener {
invalidate()
}
}
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 = nextPageNumber + 1
)
} 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)
}
}
}
Bei einer typischen PagingSource-Implementierung werden die im Konstruktor bereitgestellten Parameter an die Methode load übergeben, um die entsprechenden Daten für eine Abfrage zu laden. Im obigen Beispiel sind dies die folgenden Parameter:
backend: eine Instanz des Backend-Dienstes, der die Daten bereitstelltquery: die Suchanfrage, die an den durchbackendangegebenen Dienst gesendet werden soll
Das Objekt LoadParams enthält Informationen zum auszuführenden Ladevorgang. Dazu gehören der zu ladende Schlüssel und die Anzahl der zu ladenden Elemente. Wenn Sie in load zwischen den Ladetypen unterscheiden möchten, prüfen Sie, welche LoadParams-Unterklasse übergeben wurde: LoadParams.Refresh, LoadParams.Append oder LoadParams.Prepend.
Das Objekt LoadResult enthält das Ergebnis des Ladevorgangs. LoadResult ist eine versiegelte Klasse, die je nachdem, ob der load-Aufruf erfolgreich war, eine von drei Formen annimmt:
- Wenn das Laden erfolgreich ist, wird ein
LoadResult.Page-Objekt zurückgegeben. Übergeben SienullfürnextKeyoderprevKey, wenn in dieser Richtung keine weiteren Daten geladen werden können. - Wenn das Laden nicht erfolgreich ist, wird ein
LoadResult.Error-Objekt zurückgegeben. - Wenn der
PagingSourcenicht mehr gültig ist, weil sich die zugrunde liegenden Daten geändert haben, geben Sie einLoadResult.Invalid-Objekt zurück.
Die folgende Abbildung zeigt, wie die Funktion load in diesem Beispiel den Schlüssel für jeden Ladevorgang empfängt und den Schlüssel für den nachfolgenden Ladevorgang bereitstellt.
load den Schlüssel verwendet und aktualisiert.
Die PagingSource-Implementierung muss auch eine getRefreshKey-Methode implementieren, die ein PagingState-Objekt als Parameter verwendet. Sie gibt den Schlüssel zurück, der an die Methode load übergeben werden soll, wenn die Daten nach dem ersten Laden aktualisiert oder ungültig gemacht werden. Die Paging-Bibliothek ruft diese Methode bei nachfolgenden Aktualisierungen der Daten automatisch auf. Bei der offsetbasierten Paginierung, bei der Schlüssel Elementpositionen darstellen, können Sie den Aktualisierungsschlüssel relativ zu state.anchorPosition berechnen:
// Only for positional or offset PagingSources where Key is a 0-based item index
// rather than a page number.
override fun getRefreshKey(state: PagingState<Int, User>): Int? {
// Center the refreshed load around the user's current viewport so the list
// does not jump back to the top on invalidation.
return state.anchorPosition?.let { anchorPosition ->
(anchorPosition - state.config.initialLoadSize / 2).coerceAtLeast(0)
}
}
Fehler verarbeiten
Anfragen zum Laden von Daten können aus verschiedenen Gründen fehlschlagen, insbesondere beim Laden über ein Netzwerk. Melden Sie Fehler, die beim Laden aufgetreten sind, indem Sie ein LoadResult.Error-Objekt von der load-Methode zurückgeben.
Sie können beispielsweise Ladefehler in ExamplePagingSource aus dem vorherigen Beispiel abfangen und melden, indem Sie der Methode load Folgendes hinzufügen:
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)
}
Weitere Informationen zum Umgang mit Retrofit-Fehlern finden Sie in den Beispielen in der PagingSource API-Referenz.
PagingSource erfasst LoadResult.Error-Objekte und stellt sie der Benutzeroberfläche zur Verfügung, damit Sie darauf reagieren können. Weitere Informationen zum Anzeigen des Ladestatus in der Benutzeroberfläche finden Sie unter Ladestatus verwalten und darstellen.
Stream von PagingData einrichten
Als Nächstes benötigen Sie einen Stream mit paginierten Daten aus der PagingSource-Implementierung.
Richten Sie den Datenstream in Ihrem ViewModel ein. Die Klasse Pager bietet Methoden, die einen reaktiven Stream von PagingData-Objekten aus einer PagingSource bereitstellen. Die Paging-Bibliothek stellt den Datenstream als Flow bereit.
Wenn Sie eine Pager-Instanz erstellen, um Ihren reaktiven Stream einzurichten, müssen Sie der Instanz ein PagingConfig-Konfigurationsobjekt und eine Funktion bereitstellen, die Pager mitteilt, wie eine Instanz Ihrer PagingSource-Implementierung abgerufen wird. Sie können auch einen optionalen initialKey angeben, z. B. 1 für 1-basierte Seitennummern oder 0 für offsetbasiertes Paging. In PagingConfig können Sie pageSize, initialLoadSize, prefetchDistance und enablePlaceholders konfigurieren. Die Property prefetchDistance definiert, wie viele Elemente von einem der beiden Ränder des geladenen Inhalts den nächsten Abruf auslösen.
class UserViewModel(
private val backend: ExampleBackendService,
private val query: String
) : ViewModel() {
val userPagingFlow: Flow<PagingData<User>> = Pager(
config = PagingConfig(
pageSize = 20,
// Load 3 pages initially so the user can scroll immediately without
// hitting a loading state.
initialLoadSize = 60,
// Fetch the next page far enough ahead of the scroll position to
// avoid visible pauses.
prefetchDistance = 20,
enablePlaceholders = true
),
initialKey = 1,
pagingSourceFactory = {
ExamplePagingSource(backend, query)
}
)
.flow
.cachedIn(viewModelScope)
}
Der Operator cachedIn macht den Datenstream freigebbar und speichert die geladenen Daten mit dem angegebenen CoroutineScope im Cache. Ohne cachedIn kann die PagingData nicht wiederhergestellt werden. In diesem Beispiel wird die viewModelScope verwendet, die vom Lebenszyklus-Artefakt lifecycle-viewmodel-ktx bereitgestellt wird.
Das Pager-Objekt ruft die Methode load aus dem PagingSource-Objekt auf, stellt ihr das LoadParams-Objekt zur Verfügung und empfängt im Gegenzug das LoadResult-Objekt.
Daten erfassen und auf der Benutzeroberfläche anzeigen lassen
Um den seitenbasierten Stream mit der Benutzeroberfläche zu verbinden, rufen Sie den Ablauf aus Ihrem ViewModel ab und übergeben Sie ihn an Ihr Listen-Composable.
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val userFlow = viewModel.userPagingFlow
UserList(flow = userFlow)
}
Verwenden Sie collectAsLazyPagingItems, um den PagingData-Flow in LazyPagingItems zu konvertieren. Verwenden Sie dann die items API in einem LazyColumn, um die einzelnen Elemente anzuordnen.
Achten Sie darauf, für jeden Artikel eine eindeutige, unveränderliche Kennung mit itemKey anzugeben.
Im folgenden Beispiel wird it.id (mit Verweis auf das Attribut User.id) verwendet, da es für die Instanz User bei Datenaktualisierungen stabil bleibt.
@Composable
fun UserList(flow: Flow<PagingData<User>>) {
val lazyPagingItems = flow.collectAsLazyPagingItems()
LazyColumn {
items(
lazyPagingItems.itemCount,
key = lazyPagingItems.itemKey { it.id }
) { index ->
val user = lazyPagingItems[index]
if (user != null) {
UserRow(user)
} else {
UserPlaceholder()
}
}
}
}
Die Paging-Bibliothek verwendet null für Platzhalter, während eine Seite geladen wird. Wenn Sie Platzhalter aktiviert haben, müssen Sie daher null-Werte im Inhaltsblock verarbeiten.
Jetzt werden die Daten in der Liste seitenweise angezeigt und die Paging-Bibliothek lädt zusätzliche Seiten, wenn der Nutzer scrollt.
Zusätzliche Ressourcen
Weitere Informationen zur Paging-Bibliothek finden Sie in den folgenden zusätzlichen Ressourcen:
Dokumentation
Inhalte ansehen
Empfehlungen für Sie
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Seite aus Netzwerk und Datenbank
- Zu Paging 3 migrieren
- Paging-Bibliothek – Übersicht