paginated 1.0.0
paginated: ^1.0.0 copied to clipboard
A simple, unopinionated Flutter package that adds pagination to your existing scrollable widgets like ListView, GridView, SliverList, and SliverGrid.
Paginated #

A simple, unopinionated Flutter package that adds pagination to your existing scrollable widgets. Just wrap your ListView
, GridView
, SliverList
, or SliverGrid
with Paginated
and you're done!
Why Paginated? #
- 🎯 Unopinionated: You manage your own state, data, and API calls
- 🔄 Simple: Just wrap your existing scrollable widgets
- 🎨 Flexible: Works with any state management solution
- ⚡ Lightweight: Minimal overhead, maximum control
- 🛠️ Compatible: Supports ListView, GridView, SliverList, and SliverGrid
Unlike other pagination packages, Paginated
doesn't impose any architectural decisions on your app or make you use custom implementations of Lists and Grids.
Getting Started #
Add paginated
to your pubspec.yaml
:
dependencies:
paginated: ^1.0.0
Basic Usage #
class MyPaginatedList extends StatefulWidget {
@override
_MyPaginatedListState createState() => _MyPaginatedListState();
}
class _MyPaginatedListState extends State<MyPaginatedList> {
final List<String> _items = [];
bool _canFetchNextPage = true;
bool _hasError = false;
@override
void initState() {
super.initState();
_fetchNextPage();
}
Future<void> _fetchNextPage() async {
try {
final newItems = await ApiService.fetchItems();
setState(() {
_items.addAll(newItems);
_canFetchNextPage = newItems.isNotEmpty;
_hasError = false;
});
} catch (e) {
setState(() {
_hasError = true;
});
}
}
@override
Widget build(BuildContext context) {
return Paginated(
onFetchNextPage: _fetchNextPage,
canFetchNextPage: _canFetchNextPage,
hasError: _hasError,
loadingBuilder: (context, index) => const Center(
child: CircularProgressIndicator(),
),
errorBuilder: (context) => Column(
children: [
const Icon(Icons.error, color: Colors.red),
const Text('Failed to load more items'),
ElevatedButton(
onPressed: () => setState(() => _hasError = false),
child: const Text('Retry'),
),
],
),
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) => ListTile(
title: Text(_items[index]),
),
),
);
}
}
Initial Empty State Behavior #
When your scrollable widget is initially empty (itemCount == 0), the Paginated
widget returns the child unchanged without any loading indicators. This is intentional behavior - the package will not auto-trigger an initial load when a list has no items.
You are expected to:
- Fetch the first page of data manually (typically in
initState
or when the screen loads) - Handle the initial empty state with your own loading UI
This design keeps the package unopinionated and gives you full control over the initial loading experience.
Examples for All Scrollable Types #
ListView #
Paginated(
onFetchNextPage: _fetchNextPage,
canFetchNextPage: _canFetchNextPage,
loadingBuilder: (context, index) => const Padding(
padding: EdgeInsets.all(16.0),
child: Center(child: CircularProgressIndicator()),
),
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) => ListTile(
title: Text(_items[index]),
),
),
)
GridView #
Paginated(
onFetchNextPage: _fetchNextPage,
canFetchNextPage: _canFetchNextPage,
loadersCount: 2, // Show 2 loading indicators to match grid columns
loadingBuilder: (context, index) => Card(
child: Center(child: CircularProgressIndicator()),
),
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: _items.length,
itemBuilder: (context, index) => Card(
child: Center(child: Text(_items[index])),
),
),
)
SliverList (within CustomScrollView) #
CustomScrollView(
slivers: [
SliverAppBar(title: Text('My App')),
Paginated(
onFetchNextPage: _fetchNextPage,
canFetchNextPage: _canFetchNextPage,
loadingBuilder: (context, index) => SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Center(child: CircularProgressIndicator()),
),
),
child: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text(_items[index])),
childCount: _items.length,
),
),
),
],
)
SliverGrid (within CustomScrollView) #
CustomScrollView(
slivers: [
SliverAppBar(title: Text('My App')),
Paginated(
onFetchNextPage: _fetchNextPage,
canFetchNextPage: _canFetchNextPage,
loadersCount: 3, // Match your grid's cross axis count
loadingBuilder: (context, index) => SliverToBoxAdapter(
child: Card(child: Center(child: CircularProgressIndicator())),
),
child: SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
delegate: SliverChildBuilderDelegate(
(context, index) => Card(child: Center(child: Text(_items[index]))),
childCount: _items.length,
),
),
),
],
)
Features #
- Zero Configuration: No complex setup required
- State Agnostic: Works with setState, BLoC, Provider, Riverpod, etc.
- Error Handling: Customizable error handling and recovery flows
- Customizable: Control loading and error indicators
- Performance: Only renders additional items when needed
- Sliver Support: Full support for CustomScrollView layouts
Common Use Cases #
Perfect for implementing:
- Infinite scrolling in social media feeds
- Endless loading for image galleries and photo viewers
- Auto-pagination for search results and product catalogs
- Lazy loading for large datasets and API responses
- Progressive loading for news articles and blog content
- Continuous scroll experiences in e-commerce apps
API Reference #
Paginated Properties #
Property | Type | Required | Description |
---|---|---|---|
child |
Widget |
Yes | The scrollable widget to paginate (ListView, GridView, SliverList, or SliverGrid) |
onFetchNextPage |
FutureOr<void> Function() |
Yes | Callback invoked to load more data |
canFetchNextPage |
bool |
Yes | Whether more data can be loaded |
loadingBuilder |
IndexedWidgetBuilder |
Yes | Builder for loading indicators |
hasError |
bool |
No | Whether an error occurred (default: false) |
errorBuilder |
WidgetBuilder? |
If hasError is true |
Builder for error state (required if hasError is true) |
loadersCount |
int |
No | Number of loading indicators to show (default: 1) |
Additional Resources #
- 📖 Example Project: Check the
/example
folder for a complete example - 🐛 Issues: Report bugs or request features on GitHub Issues
License #
This project is licensed under the MIT License - see the LICENSE file for details.