pagination_grid_view 1.0.1
pagination_grid_view: ^1.0.1 copied to clipboard
A Flutter grid view with built-in pagination, pull-to-refresh, empty states, and loading indicators. Perfect for image galleries, product grids, and any paginated grid layout.
pagination_grid_view #
A Flutter grid view widget with built-in pagination support. Automatically loads more items when scrolling near the bottom.
Features #
📱 Auto Pagination - Loads more data automatically when scrolling
🔄 Pull to Refresh - Built-in refresh support
🎨 Customizable - Loading indicators, empty states, error widgets
📐 Responsive - Fixed columns or max extent mode
⚡ Zero Dependencies - Pure Flutter
🎯 Type Safe - Generic support for any data type
🚀 Easy to Use - Simple, intuitive API
Installation #
dependencies:
pagination_grid_view: ^1.0.1
Quick Start #
import 'package:pagination_grid_view/pagination_grid_view.dart';
PaginationGridView<Product>(
items: products,
onLoadMore: () async {
final newProducts = await fetchMoreProducts();
setState(() => products.addAll(newProducts));
},
itemBuilder: (context, product, index) => ProductCard(product),
crossAxisCount: 2,
hasMoreData: hasMoreProducts,
)
Usage Examples #
1. Basic Grid with 2 Columns #
PaginationGridView<String>(
items: items,
onLoadMore: () async {
await Future.delayed(Duration(seconds: 1));
setState(() {
items.addAll(List.generate(10, (i) => 'Item ${items.length + i}'));
});
},
itemBuilder: (context, item, index) => Card(
child: Center(child: Text(item)),
),
crossAxisCount: 2,
hasMoreData: items.length < 100,
)
2. Image Gallery (3 Columns) #
PaginationGridView<ImageData>(
items: images,
onLoadMore: loadMoreImages,
itemBuilder: (context, image, index) => InkWell(
onTap: () => viewImage(image),
child: Image.network(
image.url,
fit: BoxFit.cover,
),
),
crossAxisCount: 3,
childAspectRatio: 1.0,
crossAxisSpacing: 4,
mainAxisSpacing: 4,
hasMoreData: hasMoreImages,
padding: EdgeInsets.all(4),
)
3. Product Grid with Pull-to-Refresh #
PaginationGridView<Product>(
items: products,
onLoadMore: () async {
final newProducts = await api.fetchProducts(page: currentPage);
setState(() {
products.addAll(newProducts);
currentPage++;
});
},
onRefresh: () async {
final freshProducts = await api.fetchProducts(page: 1);
setState(() {
products = freshProducts;
currentPage = 2;
});
},
itemBuilder: (context, product, index) => ProductCard(
product: product,
onTap: () => Navigator.push(...),
),
crossAxisCount: 2,
hasMoreData: currentPage <= totalPages,
enablePullToRefresh: true,
)
4. Responsive Grid (Max Extent) #
Automatically adjusts column count based on screen width:
PaginationGridView.extent(
items: products,
onLoadMore: loadMore,
itemBuilder: (context, product, index) => ProductCard(product),
maxCrossAxisExtent: 200, // Max width per item
hasMoreData: hasMore,
)
5. Custom Loading & Empty States #
PaginationGridView<Item>(
items: items,
onLoadMore: loadMore,
itemBuilder: (context, item, index) => ItemCard(item),
crossAxisCount: 2,
hasMoreData: hasMore,
isLoading: isLoadingInitial,
// Custom loading indicator
loadingWidget: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(color: Colors.purple),
SizedBox(height: 16),
Text('Loading awesome content...'),
],
),
// Custom empty state
emptyWidget: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.search_off, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text('No items found'),
SizedBox(height: 8),
ElevatedButton(
onPressed: () => reload(),
child: Text('Reload'),
),
],
),
)
6. With Error Handling #
PaginationGridView<Product>(
items: products,
onLoadMore: () async {
try {
final newProducts = await api.fetchProducts();
setState(() {
products.addAll(newProducts);
hasError = false;
});
} catch (e) {
setState(() => hasError = true);
}
},
itemBuilder: (context, product, index) => ProductCard(product),
crossAxisCount: 2,
hasMoreData: hasMore,
hasError: hasError,
errorWidget: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 64, color: Colors.red),
SizedBox(height: 16),
Text('Failed to load products'),
TextButton(
onPressed: () => retry(),
child: Text('Retry'),
),
],
),
)
7. Pinterest-Style (Different Aspect Ratios) #
// Use staggered grid package for variable heights
// Or use fixed aspect ratios:
PaginationGridView<Pin>(
items: pins,
onLoadMore: loadMore,
itemBuilder: (context, pin, index) => PinCard(pin),
crossAxisCount: 2,
childAspectRatio: 0.7, // Taller items
hasMoreData: hasMore,
)
API Reference #
PaginationGridView Parameters #
| Parameter | Type | Default | Description |
|---|---|---|---|
items |
List<T> |
required | List of items to display |
itemBuilder |
Widget Function(...) |
required | Builder for each item |
onLoadMore |
Future<void> Function() |
required | Called to load more data |
hasMoreData |
bool |
required | Whether there's more data |
crossAxisCount |
int |
2 | Number of columns |
childAspectRatio |
double |
1.0 | Width/height ratio |
crossAxisSpacing |
double |
10.0 | Horizontal spacing |
mainAxisSpacing |
double |
10.0 | Vertical spacing |
enablePullToRefresh |
bool |
true | Enable refresh gesture |
onRefresh |
Future<void> Function()? |
null | Refresh callback |
isLoading |
bool |
false | Initial loading state |
loadingWidget |
Widget? |
null | Custom loading indicator |
emptyWidget |
Widget? |
null | Custom empty state |
errorWidget |
Widget? |
null | Custom error widget |
hasError |
bool |
false | Error state |
padding |
EdgeInsetsGeometry? |
16 all | Grid padding |
loadMoreThreshold |
double |
0.8 | Trigger distance (0-1) |
showLoadingAtBottom |
bool |
true | Show bottom loader |
scrollController |
ScrollController? |
null | Custom controller |
physics |
ScrollPhysics? |
null | Scroll physics |
shrinkWrap |
bool |
false | Shrink wrap |
primary |
bool? |
null | Primary scroll view |
reverse |
bool |
false | Reverse grid |
PaginationGridView.extent Parameters #
All above parameters, plus:
| Parameter | Type | Default | Description |
|---|---|---|---|
maxCrossAxisExtent |
double |
200 | Max item width |
Common Patterns #
State Management with BLoC #
BlocBuilder<ProductBloc, ProductState>(
builder: (context, state) {
return PaginationGridView<Product>(
items: state.products,
onLoadMore: () => context.read<ProductBloc>().add(LoadMore()),
onRefresh: () => context.read<ProductBloc>().add(Refresh()),
itemBuilder: (context, product, index) => ProductCard(product),
crossAxisCount: 2,
hasMoreData: state.hasMore,
isLoading: state.isLoading,
hasError: state.hasError,
);
},
)
With Provider #
Consumer<ProductProvider>(
builder: (context, provider, child) {
return PaginationGridView<Product>(
items: provider.products,
onLoadMore: provider.loadMore,
onRefresh: provider.refresh,
itemBuilder: (context, product, index) => ProductCard(product),
crossAxisCount: 2,
hasMoreData: provider.hasMore,
isLoading: provider.isLoading,
);
},
)
Tips & Best Practices #
- Avoid Rebuilding: Use
constconstructors where possible - Handle Errors: Always implement error handling in
onLoadMore - Debounce Loads: Prevent multiple simultaneous load requests
- Cache Data: Cache loaded items to improve performance
- Responsive: Use
.extent()for responsive layouts - Images: Use
cached_network_imagefor better performance
Use Cases #
✅ Product grids (e-commerce)
✅ Image galleries
✅ Video thumbnails
✅ User avatars
✅ Social media feeds (grid view)
✅ File browsers
✅ Icon pickers
✅ Category selectors
License #
MIT License - see LICENSE file.