A lapozás — az adatok oldalankénti betöltésének technikája, amelyet mobil alkalmazásokban és webszolgáltatásokban használnak nagy adatkészletekkel való munkához. A Android Developers Documentation (2025) szerint a lapozás helyes implementációja csökkenti az API terhelését, forgalmat takarít meg és javítja a felhasználói élményt. Az oldalankénti betöltés lehetővé teszi az alkalmazás számára, hogy a tartalmat fokozatosan jelenítse meg, anélkül hogy meg kellene várnia az összes adat teljes betöltését.
Főbb pontok
Lapozás (angolul pagination — oldalakra osztás) — egy technika nagy adatkészletek egymást követő adagokra (oldalakra) bontásához. Mobil alkalmazásokban a lapozást üzenetlisták, hírfolyamok, termékkatalógusok, rendelési előzmények és bármely más, potenciálisan korlátlan számú rekordot tartalmazó gyűjtemény betöltésekor használják.
Lapozás nélkül az alkalmazásnak az összes adatot egyszerre kell betöltenie, ami hosszú várakozáshoz, nagy forgalomfogyasztáshoz és instabil működéshez vezet gyengébb eszközökön. Az API-kérés lapozással csak egy adag adatot és meta-információt ad vissza a következő betöltéséhez — így az alkalmazás ellenőrzi a kapott információ mennyiségét.
A lapozás fő mérőszámai: az oldal mérete (page size) — a rekordok száma egy oldalon (általában 10-50), és az oldalszám vagy kurzor — az aktuális pozíció mutatója a készletben. Az oldalméret kiválasztása az adatok típusától függ: kompakt elemekhez (nevek) 20-30 elegendő, képes kártyákhoz — 10-15.
A mobileszközök korlátozott erőforrásokkal rendelkeznek: RAM memória mennyisége, processzorsebesség és forgalmi korlátok. A lapozás három kulcsfontosságú feladatot old meg: a memóriafogyasztás csökkentése (csak a látható elemek tárolódnak a memóriában), az első megjelenítés felgyorsítása (az első adag gyorsabban betöltődik, mint a teljes készlet) és a forgalom megtakarítása (az adatok csak akkor töltődnek be, amikor a felhasználó görgeti a listát).
Négy fő lapozási típus létezik, amelyek mindegyike specifikus feladatokat old meg. A módszer kiválasztása az adatkonzisztenciára vonatkozó követelményektől, az API architektúrájától, a tárolási típustól és az ügyfél- és szerveroldali implementáció megengedett összetettségétől függ.
| Típus | Működési elv | Stabilitás | Sebesség nagy mennyiségnél |
|---|---|---|---|
| Offset | LIMIT + OFFSET SQL-ben | alacsony | csökken az OFFSET növekedésével |
| Cursor | WHERE id > last_id | magas | stabil (O(log n)) |
| Keyset | WHERE key > last_key | magas | stabil (O(log n)) |
| Time-based | WHERE created_at < last_time | közepes | stabil indexszel |
Az Offset lapozás statikus vagy ritkán frissülő adatkészletekhez alkalmas, amikor az egyszerű implementáció fontos. A Cursor és Keyset — dinamikus adatokhoz gyakori beszúrásokkal. A Time-based — kronológikus hírfolyamokhoz, ahol a rekordok a létrehozás ideje szerint vannak rendezve. A GraphQL szabvány Relay a cursor lapozást használja az egyetlen ajánlott módszerként.
Az Offset lapozás — az oldalankénti betöltés legegyszerűbb típusa. Az ügyfél page és limit (vagy offset és limit) paramétereket küld, a szerver alkalmazza az SQL OFFSET és LIMIT utasításokat. Például page=2, limit=20 a 21-40 rekordokat adja vissza. Ez a módszer intuitívan érthető és könnyen implementálható bármilyen technológiai stacken.
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
}
Az Offset lapozás fő hátránya — a kihagyott és duplikált rekordok problémája. Ha két kérés között új rekordok kerülnek a táblába, az OFFSET eltolódik: a felhasználó kétszer láthatja ugyanazt a rekordot, vagy kihagyhat egy újat. Ez kritikus a hírfolyamok és chat-ek esetében, ahol fontos a konzisztencia.
Egy másik probléma — a teljesítmény csökkenése nagy OFFSET értékeknél. Az adatbázisnak be kell szkennelnie és át kell ugrania az első offset rekordot az eredmény visszaadása előtt. offset=100000 esetén még LIMIT 20 mellett is észrevehető időt tölt a szerver a szkenneléssel. A PostgreSQL és MySQL lineáris sebességcsökkenést mutat az OFFSET növekedésével.
Az Offset lapozás továbbra is a legjobb választás a következőkhöz: adminisztrációs panelek (az adatok ritkán változnak, oldalak közötti navigáció szükséges), jelentések és historikus naplók (rögzített adatmet-szet), szűréssel ellátott katalógusok (bármely oldalra navigálhat). Az Offset a legkönnyebben implementálható az ügyféloldalon is — a RecyclerView Paging 3-mal azonnal támogatja.
A Keyset lapozás egyedi kulcsot (általában elsődleges kulcsot) használ a rekordok szűréséhez. Az OFFSET helyett a kérés WHERE id > last_seen_id utasítást használ. Ez stabil teljesítményt biztosít a rekordok számától függetlenül, és nem keletkeznek duplikátumok beszúráskor, mivel az új rekordoknak mindig nagyobb az id-jük.
-- Offset-pagináció (problémás)
SELECT * FROM posts
ORDER BY id
LIMIT 20 OFFSET 100;
-- Keyset-pagináció (stabil)
SELECT * FROM posts
WHERE id > 100
ORDER BY id
LIMIT 20;
A Time-based lapozás (vagy időkurzor) a created_at időbélyeget használja a navigációhoz. Az ügyfél elküldi az utoljára betöltött rekord timestamp-jét, a szerver visszaadja az ezen időbélyeg előtt vagy után létrehozott rekordokat. A módszer népszerű a közösségi hálózatokban és hírfolyamokban, ahol a rekordok sorrendjét a közzététel ideje határozza meg.
A Time-based lapozás sajátossága — lehetséges duplikátumok, ha két rekord ugyanabban a ezredmásodpercben jött létre. A probléma kiküszöbölésére a time-based kulcsot egyedi id-vel kombinálják: WHERE (created_at, id) < (last_time, last_id). Az ilyen összetett kurzor garantálja az egyes rekordok egyediségét és a pontos sorrendet.
A Keyset lapozás egyedi és monoton növekvő értékű oszlopot igényel (auto-increment id, UUID v7). A Time-based bármely created_at mezővel rendelkező táblához alkalmas, de a duplikátumok további feldolgozását igényli. A fő különbség: a Keyset stabilan működik bármilyen beszúrási műveletnél, míg a Time-based érzékeny az azonos időbélyegekre.
A lapozási típus kiválasztása az adatok jellegétől és a felhasználói élményre vonatkozó követelményektől függ. Az alábbiakban ajánlások találhatók tipikus mobilfejlesztési forgatókönyvekhez. Nincs univerzális megoldás — minden módszernek van olyan alkalmazási területe, ahol optimális.
Az Android Paging 3 könyvtár a PagingSource segítségével támogatja az összes lapozási típust. Az Offsethez — PagingSource Int kulccsal (page), a Cursorhoz — String vagy Long kulccsal (cursor). A PagingSource automatikusan kezeli a betöltést, gyorsítótárazást és az újrapróbálkozásokat hibák esetén.
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
}
}
}
Az oldal mérete befolyásolja a betöltési sebességet és a teljesítmény érzékelését. Mobil alkalmazások esetében az optimális tartomány 10-25 elem oldalanként. Kevesebb mint 10 — túl gyakori API-kérések és szaggatott görgetés. Több mint 25 — az első adag hosszú betöltése lassú hálózatokon.
Képek és videók esetében az oldal méretét 5-10-re csökkentik, mivel minden elem további időt igényel a média betöltéséhez. Szöveges elemekből álló listákhoz (hozzászólások, naplók) a méret 30-50 rekordra növelhető. Javasolt, hogy az oldal mérete az API-n keresztül konfigurálható legyen, hogy az ügyfél alkalmazkodni tudjon a különböző hálózati körülményekhez.
Gyakran Ismételt Kérdések
A lapozás — az adatok adagokban történő betöltése, nem pedig egyszerre. Mint egy könyvben: elolvasol egy oldalt, majd átlapozol a következőre. Egy alkalmazásban ez azt jelenti, hogy a lista görgetésekor a következő adatadag töltődik be, nem a teljes lista egyszerre, ami forgalmat és memóriát takarít meg.
Az Offset számolja a rekordokat: „hagyj ki 20-at, add vissza a következő 10-et„. Ha a betöltések között új rekord kerül a táblába — a számozás eltolódik. A Cursor az utolsó rekord egyedi azonosítóját használja: „adj vissza 10 rekordot ID = 100 után„. Az új rekordok nem befolyásolják a pozíciót.
Mobil alkalmazásokhoz 10-25 elem az optimális. Képeket tartalmazó listákhoz — 5-10, szöveges hírfolyamokhoz — 20-30. A méret egy elem átlagos méretétől függ: minél nehezebb egy elem, annál kisebbnek kell lennie az oldalnak a gyors megjelenítéshez.
Használja az Android Jetpack Paging 3 könyvtárát. Ez biztosítja a PagingSource-t a betöltéshez, a PagingData-t a reaktív adatfolyamhoz és a PagingDataAdapter-t az automatikus betöltéshez görgetés közben. A könyvtár támogatja az Offset, Cursor és Keyset lapozást egyéni PagingSource segítségével.
A végtelen görgetés — egy UI-minta, ahol egy új adatadag automatikusan betöltődik a lista végéhez közeledve. A lapozás — az adatok adagokban történő betöltésének mechanizmusa, a végtelen görgetés pedig az egyik megjelenítési módja. Alternatíva a „Több betöltése„ gomb.
Összefoglaló
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is