Pagination
The ODM provides a fully type-safe pagination API that eliminates an entire class of common Firestore bugs.
The Concept
Traditional Firestore pagination is error-prone because the sorting logic (orderBy
) and the pagination cursor (startAfter
) are defined separately. If they don't match perfectly in the number, order, and type of fields, you get a runtime error.
This ODM solves this by making the orderBy
clause the single source of truth. The methods you use for pagination, like startAfterObject
, use the information from the orderBy
clause to automatically and safely construct the correct cursor.
How to Paginate
Here is the standard pagination flow:
- Create an ordered query: You must use
.orderBy()
to define the sorting for your pagination. - Fetch the first page: Use
.limit()
to get the first batch of documents. - Get the cursor: The last document of the first page will serve as the cursor for the next page.
- Fetch the next page: Use a pagination method like
.startAfterObject()
with the cursor document.
// 1. Create a query, ordered by follower count
final query = db.users.orderBy(($) => ($.profile.followers(descending: true),));
// 2. Fetch the first page of 20 users
final firstPage = await query.limit(20).get();
// 3. Get the last user from the first page to use as a cursor
if (firstPage.isNotEmpty) {
final lastUser = firstPage.last;
// 4. Fetch the next page of 20 users
// The ODM automatically and safely extracts the 'followers' value from `lastUser`
// to use as the cursor.
final nextPage = await query.startAfterObject(lastUser).limit(20).get();
}
Pagination Methods
Object-Based Cursors (Recommended)
These methods are the safest and easiest to use. They automatically extract the correct cursor values from a model object you provide.
startAtObject(T object)
startAfterObject(T object)
endAtObject(T object)
endBeforeObject(T object)
Value-Based Cursors
If you need more control, you can provide the cursor values manually. The ODM will still provide type-safety, ensuring the values you provide match the type signature of your orderBy
clause.
startAt(O cursorValues)
startAfter(O cursorValues)
endAt(O cursorValues)
endBefore(O cursorValues)
// Manually providing a cursor value.
// The type signature of orderBy is (int,), so startAfter expects an int tuple.
final nextPage = await db.users
.orderBy(($) => ($.profile.followers(descending: true),))
.startAfter((1000,)) // Manually provide the follower count to start after
.limit(20)
.get();