How to Handle Large Lists in Flutter with Smart Pagination Strategies
Teqani Blogs
Writer at Teqani
Scrolling through endless data in an app feels effortless — until you’re the one building it! This article provides a comprehensive guide to implementing pagination in Flutter, focusing on strategies to efficiently handle large lists and prevent performance issues. We'll cover manual implementation, utilizing packages, and advanced techniques like cursor-based pagination.
Why Should You Care About Pagination?
Fetching everything at once from an API is a disaster waiting to happen. The more data you pull upfront, the more memory you chew up, and the slower your app gets. Nobody enjoys a laggy app. Pagination fixes this by loading smaller sets of data over time, keeping your app fast and your users happy.
Manual Pagination: ListView and ScrollController
If you want full control, you can build pagination manually using ListView.builder
and a ScrollController
.
Set Up Scroll Detection
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent) {
loadMoreItems();
}
});
}
Fetch More Items
Future<void> loadMoreItems() async {
setState(() => isLoading = true);
final response = await http.get(Uri.parse('https://api.example.com/data?page=$currentPage'));
if (response.statusCode == 200) {
final newItems = jsonDecode(response.body);
setState(() {
items.addAll(newItems);
currentPage++;
isLoading = false;
});
}
}
Build List with Loader
ListView.builder(
controller: _scrollController,
itemCount: items.length + 1,
itemBuilder: (context, index) {
if (index == items.length) {
return isLoading ? Center(child: CircularProgressIndicator()) : SizedBox();
}
return ListTile(title: Text(items[index].title));
},
);
Pro Tip: Always add a loading spinner at the bottom when fetching more!
Easy Pagination with infinite_scroll_pagination
If manual management feels tedious, the infinite_scroll_pagination
package can make your life much easier.
Quick Example:
final PagingController<int, Item> _pagingController = PagingController(firstPageKey: 1);
@override
void initState() {
super.initState();
_pagingController.addPageRequestListener((pageKey) {
fetchItems(pageKey);
});
}
Future<void> fetchItems(int page) async {
final newItems = await fetchItemsFromApi(page);
final isLastPage = newItems.length < pageSize;
if (isLastPage) {
_pagingController.appendLastPage(newItems);
} else {
_pagingController.appendPage(newItems, page + 1);
}
}
It manages loading states, errors, and pagination logic under the hood — so you can focus on building a great UI.
Pagination with GetX: Reactive and Simple
Using GetX for pagination is incredibly powerful, especially when your app is already using it for state management.
class ProductController extends GetxController {
var products = <Product>[].obs;
int page = 1;
var isLoading = false.obs;
void loadProducts() async {
if (isLoading.value) return;
isLoading(true);
final newProducts = await ApiService.fetchProducts(page);
products.addAll(newProducts);
page++;
isLoading(false);
}
}
Whenever new data arrives, your UI will reactively update — no manual setState
needed.
Cursor vs Offset Pagination: Know the Difference
Feature
Offset-Based | Cursor-Based
Speed on Large Datasets
Slower | Faster, real-time friendly
Implementation Complexity
Easy | Slightly harder
Data Accuracy
Might load duplicates | Highly reliable
Cursor-based pagination is your best friend if your app’s data keeps changing (like social feeds or live updates).
Combine Infinite Scroll with Pull-to-Refresh
Want to supercharge UX?
Allow users to pull down to refresh the list in addition to infinite scrolling!
RefreshIndicator(
onRefresh: () async {
items.clear();
currentPage = 1;
await loadMoreItems();
},
child: ListView.builder(
controller: _scrollController,
itemCount: items.length,
itemBuilder: (_, index) => ListTile(title: Text(items[index])),
),
);
Bonus: Add a shimmer effect while loading for that polished, professional feel.
Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: Container(height: 80, width: double.infinity, color: Colors.white),
);
Performance Tips You Shouldn’t Ignore
- Cache smartly: Store fetched pages to avoid hitting the API again.
- Optimize rebuilds: Use
const
widgets where possible and keys in lists. - Choose the right list widget:
ListView.separated
can reduce layout computation. - Debounce API Calls: Don’t trigger multiple fetches accidentally with fast scrolling.
Wrapping Up
Pagination isn’t just a nice-to-have; it’s essential for apps dealing with growing data.
Whether you roll out your own logic or lean on packages like infinite_scroll_pagination
, the goal stays the same: make scrolling buttery-smooth and reliable.
Different apps call for different strategies:
- Simple apps? Manual pagination is fine.
- Growing apps? Use packages or state management like GetX.
- Dynamic real-time apps? Go for cursor-based pagination.
Have you tried building infinite scroll in Flutter?
Which method worked best for you? 👇
Drop your thoughts in the comments — I’d love to hear them!
All blogs are certified by our company and reviewed by our specialists
Issue Number: #be535642-98e4-4ab5-9782-aed68b5311f8