Pagination in Flutter – Step by Step
Pagination is the process of loading data in chunks, rather than fetching the full dataset at once. This is essential for performance, especially when dealing with large datasets.
1️⃣ Key Terms
| Term | Meaning |
|---|---|
| Page | Current page number being loaded (usually starts at 1) |
| Page Size | Number of items per request |
| Total Pages | Total pages returned by API |
| hasNextPage | Whether there are more pages to load |
| loading / moreLoading | Flags to show progress indicators |
2️⃣ Common Pattern
- Load the first page when the screen opens.
- Listen to scroll events.
- When the user reaches the bottom, fetch the next page if it exists.
- Append new data to the existing list.
- Show a loading spinner at the bottom while fetching.
3️⃣ Provider Setup for Pagination
class PaginationProvider extends ChangeNotifier {
List<String> items = []; // Your list of items
int page = 1;
int pageSize = 10;
int totalPages = 1;
bool moreLoading = false;
bool initialLoading = true;
// Reset pagination (call this when opening page)
void reset() {
items.clear();
page = 1;
totalPages = 1;
moreLoading = false;
initialLoading = true;
notifyListeners();
}
// Load first page
Future<void> loadInitialData() async {
reset();
await loadMore();
initialLoading = false;
notifyListeners();
}
// Load next page
Future<void> loadMore() async {
if (moreLoading || page > totalPages) return;
moreLoading = true;
notifyListeners();
try {
// Simulate API call
final response = await fakeApiCall(page, pageSize);
items.addAll(response['data']);
totalPages = response['totalPages'];
page += 1;
} catch (e) {
print("Error fetching page $page: $e");
}
moreLoading = false;
notifyListeners();
}
// Fake API call for demonstration
Future<Map<String, dynamic>> fakeApiCall(int page, int pageSize) async {
await Future.delayed(Duration(seconds: 1)); // simulate network delay
int totalItems = 42; // total items in backend
int totalPages = (totalItems / pageSize).ceil();
List<String> data = List.generate(
pageSize, (i) => "Item ${(page - 1) * pageSize + i + 1}")
.where((item) => int.parse(item.split(" ").last) <= totalItems)
.toList();
return {'data': data, 'totalPages': totalPages};
}
}
4️⃣ UI Implementation
class PaginationPage extends StatefulWidget {
@override
_PaginationPageState createState() => _PaginationPageState();
}
class _PaginationPageState extends State<PaginationPage> {
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
final provider = Provider.of<PaginationProvider>(context, listen: false);
provider.loadInitialData();
_scrollController.addListener(() {
final provider = Provider.of<PaginationProvider>(context, listen: false);
// Load next page when near bottom
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 100) {
provider.loadMore();
}
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer<PaginationProvider>(
builder: (context, provider, child) {
if (provider.initialLoading) {
return Center(child: CircularProgressIndicator());
}
if (provider.items.isEmpty) {
return Center(child: Text("No items found"));
}
return ListView.builder(
controller: _scrollController,
itemCount: provider.items.length + 1, // extra item for loader
itemBuilder: (context, index) {
if (index == provider.items.length) {
// Show loading indicator at bottom
return provider.moreLoading
? Padding(
padding: const EdgeInsets.all(16),
child: Center(child: CircularProgressIndicator()),
)
: SizedBox();
}
return ListTile(title: Text(provider.items[index]));
},
);
},
);
}
}
5️⃣ Key Best Practices
Reset pagination when page opens.
Avoid callingresetin the scroll listener — only ininitState.-
Use flags
-
initialLoading→ for the first page. -
moreLoading→ for loading next pages.
-
Always check
page <= totalPagesbefore callingloadMore.Add bottom padding for smooth scrolling and avoid triggering too early.
Optional: Debounce scroll events for very fast scrolling.
6️⃣ Flow Recap
- Screen opens →
loadInitialData()→ fetch first page. - User scrolls → scroll listener triggers → check if near bottom →
loadMore(). - Append new items →
notifyListeners()→ rebuild list. - Loader shows while fetching next page.
This is a complete and reusable pagination template. You can adapt it for discussions, comments, videos, or any API-driven list.
If you want, I can also make a “Production-ready” version with API integration, error handling, and pull-to-refresh so it’s ready for real apps.
Do you want me to do that next?
Top comments (0)