Paginierung ist eine Technik zum seitenweisen Laden von Daten, die in mobilen Anwendungen und Webdiensten verwendet wird, um mit großen Datensätzen zu arbeiten. Laut der Android Developers Documentation (2025) reduziert eine korrekte Implementierung der Paginierung die API-Auslastung, spart Traffic und verbessert die Benutzererfahrung. Seitenweises Laden ermöglicht es der Anwendung, Inhalte schrittweise anzuzeigen, ohne auf das vollständige Laden aller Daten warten zu müssen.
Wichtige Erkenntnisse
Paginierung (von lat. pagination — Seiteneinteilung) ist eine Technik zur Aufteilung eines großen Datensatzes in aufeinanderfolgende Portionen (Seiten). In mobilen Anwendungen wird Paginierung beim Laden von Nachrichtenlisten, Nachrichtenfeeds, Produktkatalogen, Bestellverlauf und anderen Sammlungen mit potenziell unbegrenzter Anzahl von Datensätzen verwendet.
Ohne Paginierung muss die Anwendung alle Daten auf einmal laden, was zu langen Wartezeiten, hohem Traffic-Verbrauch und instabiler Leistung auf schwachen Geräten führt. Eine API-Anfrage mit Paginierung gibt nur eine Portion Daten und Metainformationen zum Laden der nächsten zurück — so kontrolliert die Anwendung die Menge der empfangenen Informationen.
Die wichtigsten Metriken der Paginierung sind: Seitengröße — Anzahl der Datensätze pro Seite (normalerweise 10–50), und Seitennummer oder Cursor — ein Zeiger auf die aktuelle Position im Datensatz. Die Wahl der Seitengröße hängt vom Datentyp ab: für kompakte Elemente (Namen) reichen 20–30, für Karten mit Bildern 10–15.
Mobile Geräte haben begrenzte Ressourcen: Arbeitsspeicher, Prozessorgeschwindigkeit und Traffic-Limits. Die Paginierung löst drei Hauptaufgaben: Reduzierung des Speicherverbrauchs (nur sichtbare Elemente werden im Speicher gehalten), Beschleunigung der ersten Anzeige (die erste Portion lädt schneller als der gesamte Satz) und Traffic-Einsparung (Daten werden nur geladen, wenn der Benutzer die Liste scrollt).
Es gibt vier Hauptarten der Paginierung, die jeweils spezifische Aufgaben lösen. Die Wahl der Methode hängt von den Anforderungen an Datenkonsistenz, der API-Architektur, der Speicherart und der akzeptablen Implementierungskomplexität auf Client- und Serverseite ab.
| Art | Funktionsprinzip | Stabilität | Geschwindigkeit bei großen Mengen |
|---|---|---|---|
| Offset | LIMIT + OFFSET in SQL | niedrig | nimmt mit wachsendem OFFSET ab |
| Cursor | WHERE id > last_id | hoch | stabil (O(log n)) |
| Keyset | WHERE key > last_key | hoch | stabil (O(log n)) |
| Time-based | WHERE created_at < last_time | mittel | stabil mit Index |
Offset-Paginierung eignet sich für statische oder selten aktualisierte Datensätze, wenn eine einfache Implementierung wichtig ist. Cursor und Keyset sind für dynamische Daten mit häufigen Einfügungen gedacht. Zeitbasierte Paginierung ist für chronologische Feeds geeignet, bei denen Datensätze nach Erstellungszeit sortiert sind. Der GraphQL-Standard Relay verwendet die Cursor-Paginierung als einzig empfohlene Methode.
Die Offset-Paginierung ist die einfachste Art des seitenweisen Ladens. Der Client sendet die Parameter page und limit (oder offset und limit), und der Server wendet SQL OFFSET und LIMIT an. Beispiel: page=2, limit=20 gibt die Datensätze 21 bis 40 zurück. Diese Methode ist intuitiv und auf jedem Stack einfach zu implementieren.
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items")
async def get_items(
page: int = Query(default=1, ge=1),
limit: int = Query(default=20, le=100)
):
offset = (page - 1) * limit
items = await fetch_items(offset, limit)
total = await count_items()
return {
"items": items,
"total": total,
"page": page,
"pages": (total + limit - 1) // limit
}
Der Hauptnachteil der Offset-Paginierung ist das Problem fehlender und doppelter Datensätze. Wenn zwischen zwei Anfragen neue Datensätze in die Tabelle eingefügt werden, verschiebt sich der OFFSET: Der Benutzer kann denselben Datensatz zweimal sehen oder einen neuen übersehen. Dies ist kritisch für Nachrichtenfeeds und Chats, bei denen Konsistenz wichtig ist.
Ein weiteres Problem ist die Leistungsverschlechterung bei großen OFFSET-Werten. Die Datenbank muss die ersten offset Datensätze scannen und überspringen, bevor sie das Ergebnis zurückgibt. Bei offset=100000 selbst mit LIMIT 20 wird der Server eine merkliche Zeit mit Scannen verbringen. PostgreSQL und MySQL zeigen einen linearen Geschwindigkeitsabfall mit wachsendem OFFSET.
Die Offset-Paginierung bleibt die beste Wahl für: Admin-Panels (Daten ändern sich selten, Seitennavigation ist erforderlich), Berichte und historische Logs (fester Daten-Snapshot), Kataloge mit Filterung (man kann zu jeder Seite springen). Offset ist auch am einfachsten auf dem Client zu implementieren — RecyclerView mit Paging 3 unterstützt es von Haus aus.
Keyset-Paginierung verwendet einen eindeutigen Schlüssel (normalerweise den Primärschlüssel) zum Filtern von Datensätzen. Statt OFFSET verwendet die Abfrage WHERE id > last_seen_id. Dies gewährleistet stabile Leistung unabhängig von der Anzahl der Datensätze und keine Duplikate bei Einfügungen, da neue Datensätze immer eine größere id haben.
-- Offset-Paginierung (problematisch)
SELECT * FROM posts
ORDER BY id
LIMIT 20 OFFSET 100;
-- Keyset-Paginierung (stabil)
SELECT * FROM posts
WHERE id > 100
ORDER BY id
LIMIT 20;
Die zeitbasierte Paginierung (oder Cursor nach Zeit) verwendet den created_at-Zeitstempel zur Navigation. Der Client sendet den Zeitstempel des zuletzt geladenen Datensatzes, und der Server gibt Datensätze zurück, die vor oder nach diesem Zeitstempel erstellt wurden. Diese Methode ist beliebt in sozialen Netzwerken und Nachrichtenfeeds, bei denen die Reihenfolge der Datensätze durch die Veröffentlichungszeit bestimmt wird.
Eine Besonderheit der zeitbasierten Paginierung sind mögliche Duplikate, wenn zwei Datensätze in derselben Millisekunde erstellt werden. Um dieses Problem zu beseitigen, kombiniert man einen zeitbasierten Schlüssel mit einer eindeutigen id: WHERE (created_at, id) < (last_time, last_id). Ein solcher zusammengesetzter Cursor garantiert die Eindeutigkeit jedes Datensatzes und eine präzise Reihenfolge.
Keyset-Paginierung erfordert eine Spalte mit einem eindeutigen und monoton steigenden Wert (Auto-Increment-ID, UUID v7). Zeitbasierte Paginierung ist für jede Tabelle mit created_at geeignet, erfordert aber zusätzliche Duplikatbehandlung. Der Hauptunterschied: Keyset arbeitet bei beliebigen Einfügevorgängen stabil, während die zeitbasierte Paginierung empfindlich auf identische Zeitstempel reagiert.
Die Wahl des Paginierungstyps hängt von der Art der Daten und den Anforderungen an die Benutzererfahrung ab. Nachfolgend finden Sie Empfehlungen für typische Szenarien in der mobilen Entwicklung. Es gibt keine universelle Lösung — jede Methode hat einen Bereich, in dem sie optimal ist.
Die Android Paging 3-Bibliothek unterstützt alle Paginierungstypen über PagingSource. Für Offset — PagingSource mit Int-Schlüssel (page), für Cursor — mit String- oder Long-Schlüssel (cursor). PagingSource verwaltet automatisch das Laden, Caching und Wiederholungsversuche bei Fehlern.
class PostPagingSource(
private val api: PostApi
) : PagingSource<Long, Post>() {
override suspend fun load(
params: LoadParams<Long>
): LoadResult<Long, Post> {
val cursor = params.key ?: Long.MAX_VALUE
return try {
val response = api.getPosts(cursor, params.loadSize)
LoadResult.Page(
data = response.items,
prevKey = null,
nextKey = response.items.lastOrNull()?.id
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Long, Post>): Long? {
return state.anchorPosition?.let {
state.closestItemToPosition(it)?.id
}
}
}
Die Seitengröße beeinflusst die Ladegeschwindigkeit und die wahrgenommene Leistung. Für mobile Anwendungen ist der optimale Bereich 10–25 Elemente pro Seite. Weniger als 10 führt zu zu vielen API-Anfragen und ruckeligem Scrollen. Mehr als 25 führt zu langsamem Laden der ersten Portion in langsamen Netzwerken.
Für Bilder und Videos reduziert man die Seitengröße auf 5–10, da jedes Element zusätzliche Zeit zum Laden von Medien benötigt. Für textbasierte Listen (Kommentare, Logs) kann die Größe auf 30–50 Datensätze erhöht werden. Es wird empfohlen, die Seitengröße über die API konfigurierbar zu machen, damit der Client sich an unterschiedliche Netzwerkbedingungen anpassen kann.
Häufig gestellte Fragen
Paginierung ist das Laden von Daten in Portionen, nicht alles auf einmal. Wie in einem Buch: Sie lesen eine Seite, dann blättern Sie zur nächsten. In einer App bedeutet das, dass beim Scrollen durch eine Liste der nächste Datenbatch geladen wird, nicht die gesamte Liste, was Traffic und Speicher spart.
Offset zählt Datensätze: „Überspringe 20, gib die nächsten 10 zurück.“ Wenn zwischen den Ladevorgängen ein neuer Datensatz hinzugefügt wird, verschiebt sich die Nummerierung. Cursor verwendet die eindeutige Kennung des letzten Datensatzes: „Gib 10 Datensätze nach ID = 100 zurück.“ Neue Datensätze beeinflussen die Position nicht.
Für mobile Anwendungen sind 10–25 Elemente optimal. Für Listen mit Bildern 5–10, für Textfeeds 20–30. Die Größe hängt von der durchschnittlichen Elementgröße ab: Je schwerer das Element, desto kleiner sollte die Seite für eine schnelle Anzeige sein.
Verwenden Sie die Paging 3-Bibliothek von Android Jetpack. Sie bietet PagingSource zum Laden, PagingData für reaktive Streams und PagingDataAdapter für automatisches Laden beim Scrollen. Die Bibliothek unterstützt Offset-, Cursor- und Keyset-Paginierung über einen benutzerdefinierten PagingSource.
Unendliches Scrollen ist ein UI-Muster, bei dem ein neuer Datenbatch automatisch geladen wird, wenn man sich dem Ende der Liste nähert. Paginierung ist der Mechanismus zum Laden von Daten in Batches, während unendliches Scrollen eine Möglichkeit zur Anzeige ist. Eine Alternative ist der „Mehr laden“-Button.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch