How to Implement Pagination in Flutter ListView

Learn how to implement pagination in Flutter ListView: lazy loading with ScrollController, infinite scroll with APIs, Riverpod and BLoC patterns, and the new PagingListener API.

Nobody loads 10,000 items at once. The question of how to implement pagination in Flutter ListView comes up in nearly every app that lists data from an API — and the correct answer has two halves: *lazy rendering* (Flutter only builds the rows on screen) and *lazy fetching* (you only download pages of data as the user scrolls). This tutorial covers both, with runnable code: ListView.builder fundamentals, the ScrollController + page/limit pattern with a bottom loading indicator, Riverpod and BLoC variants, pull-to-refresh, and the infinite_scroll_pagination package's new API (most online tutorials still show the old one — that gap is exactly why this article exists). What you'll learn Why ListView.builder is lazy and when that isn't enough Detecting scroll-to-bottom with ScrollController Fetching pages from a REST API (page/limit pattern) The hasMore / isLoadingMore state guards that prevent duplicate requests A bottom loading indicator row Pagination with Riverpod and BLoC The new PagingListener API in infinite_scroll_pagination Pull-to-refresh on a paginated list Step 0 — Understand lazy rendering first ListView builds every child up front. ListView.builder builds only what's visible (plus a small cache): ListView.builder( itemCount: items.length, itemBuilder: (context, index) => ListTile( title: Text(items[index].title), ), ); The itemBuilder is called lazily — rows are created as they scroll into view and destroyed when they leave. That's lazy rendering , and it's free. But it doesn't help if items itself contains 10,000 network-fetched objects. Lazy *fetching* — pagination — is what keeps memory and the initial load small. Step 1 — The ScrollController detection pattern ScrollController tells you where the list is. When the user…

Explore