"Supabase Flutter Pagination Tutorial: Cursor & Offset"
Supabase Flutter pagination tutorial: offset with .range(), keyset cursors on created_at + id, infinite scroll ListView, and duplicate-row fixes.
Last updated: October 3, 2026 Fetching 10,000 rows on app launch is how you get a loading spinner, an angry user, and a Supabase bill. This Supabase Flutter pagination tutorial shows you the two patterns that actually matter in production: offset pagination with .range() for simple lists, and keyset (cursor) pagination on created_at + id for feeds that stay correct when rows are inserted mid-scroll. You'll get runnable Flutter 3.x code with supabase_flutter 2.18.x for both, plus an infinite-scroll ListView and the fix for the duplicate-row bug that bites almost everyone. Heads-up on keys: Supabase migrated to publishable keys in 2026. Client init below uses an sb_publishable_ key (the legacy sb_secret_ key only works for Storage, never in client code). What you'll learn Offset pagination with .range() — the simple approach and its limits Keyset (cursor) pagination with created_at + id ordering — stable under inserts Cursor vs offset: which one to use and when Infinite scroll in Flutter with ScrollController and a loading indicator Why pagination returns duplicate or skipped rows, and how to fix it Common mistakes that break paginated lists in production Supabase Flutter Pagination Tutorial: Offset with .range() Offset pagination is the simplest mental model: "give me rows 0–19, then rows 20–39." In supabase_flutter, that's .range(from, to) . Note that .range() is inclusive on both ends , so a page of 20 is .range(0, 19) , not .range(0, 20) . There is one non-negotiable rule: always pair .range() with an explicit .order() . Without it, Postgres returns rows in an undefined order and your pages will overlap or skip rows seemingly at random. // lib/features/feed/data/feed_repository.dart import 'package:supabase_flutter/supabase_flutter.dart';…