Realm Live Results Offset Pagination Versus Snapshot SQL Pagination
0 reputation · 07 Feb 2021, 11:23 UTC
0 reputation · 07 Feb 2021, 11:23 UTC
Decide whether offset-and-limit slicing on Realm live Results can serve as stable deep pagination for a large dataset, or whether the application must manage an ordering key and re-query from a known anchor.
Realm Results are live, auto-updating collections rather than static snapshots. Objects inserted or deleted before the offset change index positions, so subsequent pages can skip or duplicate items. Realm does not provide a universal stable cursor or continuation token for local query pagination across all SDKs. Pagination entry points also differ by binding: Swift uses Results ranges and prefix, Kotlin and Java use RealmResults list operations such as subList, and .NET uses LINQ Skip and Take on IQueryable, with materialization behavior varying by SDK version. Sort plus limit supports deterministic first-N retrieval, but raw offsets alone do not guarantee stable page boundaries under concurrent writes.
35775 reputation · 07 Feb 2021, 20:55 UTC
No. Realm Results are live, auto-updating collections. An offset is an index into that live collection, not a position anchored to a specific object. If another write transaction inserts or deletes objects before the offset, the index positions shift. The next page can skip items that moved into the previous page or duplicate items that moved out of it.
Yes, for deep pagination. Query from a known anchor instead of slicing by index: order by an immutable, unique key such as a primary key, ULID, or strictly increasing timestamp, then filter key > lastSeenKey and limit to the page size. This removes dependence on index positions that change under concurrent writes.
It depends on the SDK and version. Swift Results.prefix(_) and range subscripting return live views. Kotlin and Java RealmResults.subList return a live list view backed by the same Results. .NET IQueryable.Skip and Take usually materialize when you enumerate, for example with ToList or ToArray, but re-enumerating the query re-evaluates it unless you materialize immediately. None of these provide a built-in stable cursor token for local queries.
| Item | Status | Notes |
|---|---|---|
| Results are live and auto-updating | Confirmed | Applies to local Realm across major SDKs. |
| Offset is index-based | Confirmed | Writes before the offset shift subsequent indices. |
| No universal local cursor token | Confirmed | Some sync APIs expose server tokens, but not for purely local pagination. |
| Duplicate or skipped pages under concurrent writes | Likely explanation | Occurs when the app treats a live index as a static SQL OFFSET snapshot. |
// Conceptual; adapt to your SDK
lastKey = lastItemOnPreviousPage.key
page = results
.where('key > $0', lastKey)
.sorted('key')
.limit(pageSize)
ToList the Results first, then slice by offset. Treat that slice as static; it will not reflect new writes.Do the pages need to reflect inserts and deletes while the user scrolls, or is a point-in-time snapshot acceptable? If a snapshot is acceptable, freezing and offset slicing is simpler. If pages must stay live, anchor-based keys are required.
freeze, snapshot, subList, or ToList returns a copy or a live view in your exact SDK version.Run a controlled test before changing production pagination:
where('key > lastKey') and confirm the page boundaries remain stable.// Swift example (adapt to your schema)
let page = results.sorted(byKeyPath: "key")
.offset(100)
.limit(100)
// After concurrent insert before offset, re-fetching page may differ.
// Kotlin/Java example (adapt to your schema)
RealmResults page = realm.where(Item.class)
.greaterThan("key", lastKey)
.findAll()
.sort("key")
.subList(0, pageSize);
// .NET example (adapt to your schema)
var page = realm.All()
.Where(i => i.Key > lastKey)
.OrderBy(i => i.Key)
.Take(pageSize)
.ToList();Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.