PagingSource und Pager
Festlegen, wie Seiten geladen werden
PagingSource und Pager ist eine kostenlose Android Academy-Lektion auf CoddyKit. Dies ist Lektion 2 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Android Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Android Academy-Kurs umfasst insgesamt 4 Lektionen.
Festlegen, wie Seiten geladen werden
Um Daten seitenweise aus einer Netzwerk-API zu laden, schreiben Sie eine PagingSource. Sie beantwortet für Paging 3 zwei Fragen:
- Wie lade ich die Seite für einen bestimmten Schlüssel?
- Wo soll ich nach einer Aktualisierung durch den Benutzer fortfahren?
Anschließend verpacken Sie sie in einen Pager, der daraus einen Flow<PagingData> macht.
Typparameter von PagingSource
PagingSource<Key, Value> erwartet zwei Typparameter:
- Key – identifiziert eine Seite. Bei einer API mit Seitennummern ist dies
Int, bei einer Cursor-API einString-Token. - Value – der Elementtyp, zum Beispiel
Article.
import androidx.paging.PagingSource
import androidx.paging.PagingState
class ArticlePagingSource(
private val api: ArticleApi
) : PagingSource<Int, Article>() {
// implement load() and getRefreshKey()
}load() implementieren
load() ist eine suspend-Funktion. Sie erhält params.key (die abzurufende Seite) und gibt ein LoadResult zurück.
Bei Erfolg geben Sie LoadResult.Page mit den Elementen sowie den vorherigen und nächsten Schlüsseln zurück. null für einen Schlüssel bedeutet, dass es in dieser Richtung keine weitere Seite gibt.
override suspend fun load(
params: LoadParams<Int>
): LoadResult<Int, Article> {
val page = params.key ?: 1 // first load has a null key
return try {
val response = api.getArticles(page = page, size = params.loadSize)
LoadResult.Page(
data = response.items,
prevKey = if (page == 1) null else page - 1,
nextKey = if (response.items.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}Warum prevKey und nextKey wichtig sind
Paging verwendet nextKey, um beim Herunterscrollen weitere Seiten zu laden, und prevKey, um beim Hochscrollen vorherige Seiten zu laden (nützlich, wenn Sie in der Mitte einer Liste starten).
Wenn Sie für nextKey den Wert null zurückgeben, teilen Sie Paging mit, dass es keine weiteren Seiten gibt und keine weiteren Anforderungen gestellt werden sollen. Wird dies vergessen, kann es zu unendlichen Anforderungen leerer Seiten kommen.
// Stop forward paging when the server returns an empty page
nextKey = if (response.items.isEmpty()) null else page + 1
// Stop backward paging at the first page
prevKey = if (page == 1) null else page - 1getRefreshKey() implementieren
Wenn die Liste aktualisiert wird (durch Herunterziehen zum Aktualisieren oder eine Invalidierung), muss Paging wissen, welche Seite erneut geladen werden soll, damit der Benutzer ungefähr an seiner bisherigen Position bleibt.
getRefreshKey() verwendet die aktuelle anchorPosition – das Element, das dem sichtbaren Bereich am nächsten ist –, um einen sinnvollen Schlüssel auszuwählen.
override fun getRefreshKey(state: PagingState<Int, Article>): Int? {
return state.anchorPosition?.let { anchor ->
val closestPage = state.closestPageToPosition(anchor)
closestPage?.prevKey?.plus(1)
?: closestPage?.nextKey?.minus(1)
}
}Fehler sauber behandeln
Umschließen Sie den Netzwerkaufruf mit try/catch und geben Sie bei einem Fehler LoadResult.Error(e) zurück. Paging stellt dies in der Benutzeroberfläche als LoadState.Error bereit, sodass Sie eine Schaltfläche zum erneuten Versuch anzeigen können.
Lassen Sie niemals eine Ausnahme aus load() entweichen – fangen Sie sie ab und wandeln Sie sie in LoadResult.Error um.
return try {
val response = api.getArticles(page = page, size = params.loadSize)
LoadResult.Page(
data = response.items,
prevKey = if (page == 1) null else page - 1,
nextKey = if (response.items.isEmpty()) null else page + 1
)
} catch (e: IOException) { // no network
LoadResult.Error(e)
} catch (e: HttpException) { // non-2xx response
LoadResult.Error(e)
}Einen Pager erstellen
Ein Pager verknüpft Ihre PagingConfig mit einer Factory, die eine neue PagingSource erstellt. Die Eigenschaft .flow ist ein Flow<PagingData>, den die Benutzeroberfläche sammelt.
Die Factory-Lambda muss jedes Mal eine neue Quelle erstellen, da Paging die Quelle bei einer Aktualisierung invalidiert und neu erstellt.
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
class ArticleRepository(private val api: ArticleApi) {
fun articleStream(): Flow<PagingData<Article>> = Pager(
config = PagingConfig(pageSize = 20, prefetchDistance = 5),
pagingSourceFactory = { ArticlePagingSource(api) }
).flow
}cachedIn für ViewModels
Das Sammeln von PagingData ist eine einmalige Operation; erneutes Sammeln startet den Ladevorgang neu. Damit die Daten Konfigurationsänderungen überstehen und von mehreren Sammlern gemeinsam genutzt werden können, speichern Sie den Flow mit cachedIn im viewModelScope zwischen.
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.cachedIn
class ArticleViewModel(
repo: ArticleRepository
) : ViewModel() {
val articles = repo.articleStream()
.cachedIn(viewModelScope)
}loadSize im Vergleich zu pageSize
Beachten Sie, dass load() params.loadSize liest und nicht direkt Ihre konfigurierte pageSize.
Beim allerersten Laden fordert Paging möglicherweise einen größeren Anfangsblock an (gesteuert durch initialLoadSize in PagingConfig, standardmäßig das Dreifache der Seitengröße). Übergeben Sie Ihrer API immer params.loadSize, damit die Anforderung der von Paging erwarteten Größe entspricht.
// Correct: respect the size Paging asks for
val response = api.getArticles(page = page, size = params.loadSize)
// PagingConfig can tune the first load:
PagingConfig(pageSize = 20, initialLoadSize = 40)Cursor-basierte APIs
Nicht jede API verwendet Seitennummern. Manche geben einen Cursor oder ein Token zurück, das auf die nächste Seite verweist. Das Vorgehen ist identisch – ändern Sie einfach den Key-Typ in String und verwenden Sie das Token aus der Antwort.
class CursorArticleSource(
private val api: ArticleApi
) : PagingSource<String, Article>() {
override suspend fun load(
params: LoadParams<String>
): LoadResult<String, Article> {
val cursor = params.key // null on first load
return try {
val res = api.getArticles(cursor = cursor, size = params.loadSize)
LoadResult.Page(
data = res.items,
prevKey = null, // forward-only cursor
nextKey = res.nextCursor // null when exhausted
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<String, Article>) = null
}Alles zusammenführen
Sie verfügen nun über die vollständige Datenschicht: eine PagingSource, die jeweils eine Seite lädt, einen Pager, der Seiten als Datenstrom bereitstellt, und ein ViewModel, das den Flow zwischenspeichert.
Die UI-Schicht sammelt einfach viewModel.articles – in der nächsten Lektion rendern wir dies mit einer Compose-LazyColumn.
// Data layer summary
// 1. ArticlePagingSource : PagingSource<Int, Article>
// 2. Pager(config, factory).flow -> Flow<PagingData<Article>>
// 3. ViewModel: repo.articleStream().cachedIn(viewModelScope)
// UI just collects viewModel.articlesKurze Überprüfung
Was signalisiert die Rückgabe von nextKey = null aus load() in einer PagingSource mit Seitennummern?
Zusammenfassung: PagingSource und Pager
Sie haben die Datenschicht für Paging erstellt:
PagingSource<Key, Value>implementiertload()undgetRefreshKey()load()gibtLoadResult.PagemitprevKey/nextKeyoderLoadResult.Errorzurück- Ein
null-Schlüssel beendet Paging in der jeweiligen Richtung Pager(config, factory).flowerzeugt einenFlow<PagingData>cachedIn(viewModelScope)bewahrt die Daten über Konfigurationsänderungen hinweg
Als Nächstes: Diesen Flow in einer Compose-Liste darstellen.
Häufig gestellte Fragen
Ist die Lektion „PagingSource und Pager“ kostenlos?
Ja — der vollständige Text von „PagingSource und Pager“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Android Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Android Academy-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „PagingSource und Pager“?
Festlegen, wie Seiten geladen werden Du übst Android Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um Android Academy zu starten?
Keine Vorkenntnisse erforderlich. Android Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 2 von 4.
Wie lange dauert die Lektion „PagingSource und Pager“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser Android Academy-Lektion Code schreiben und ausführen?
Ja. Jede Android Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Warum Paging?
- PagingSource und Pager
- Paging in Compose-Listen
- RemoteMediator und Caching