JetPaginator class
Simple, powerful infinite scroll pagination for any API format
JetPaginator provides a unified way to create infinite scroll lists and grids
that work with ANY API pagination format. Built on top of the official
infinite_scroll_pagination
package for maximum reliability and performance.
Key Features
- Official Package: Built on
infinite_scroll_pagination
for robust state management - Works with ANY API response format
- Support for offset-based, page-based, cursor-based, and custom pagination
- Smart Error Handling: Built-in error handling with custom error widgets
- Automatic error handling and retry functionality with proper error parsing
- Pull-to-refresh integration with Riverpod invalidate support
- Optimized builds: Efficient state management through PagingController
- Customizable loading and error indicators with error context and ref access
- Separate error handling for initial load vs. pagination errors
- List and grid layouts
Performance Optimizations
Efficient State Management:
- Uses official
PagingController
for robust state management - Automatic error handling and retry logic
- Memory-efficient handling of large lists
- Built-in animation support for smooth transitions
- Optimized scroll performance
Usage Examples
DummyJSON API (offset-based):
JetPaginator.list<Product>(
fetchPage: (pageKey) => api.getProducts(skip: pageKey, limit: 20),
parseResponse: (response, pageKey) => PageInfo(
items: (response['products'] as List)
.map((json) => Product.fromJson(json))
.toList(),
nextPageKey: response['skip'] + response['limit'] < response['total']
? response['skip'] + response['limit']
: null,
),
itemBuilder: (product, index) => ProductCard(product: product),
)
With Provider Support (recommended for non-family providers):
// Define a simple provider (not family)
final allProductsProvider = FutureProvider<List<Product>>((ref) async {
// Fetch all products logic here
return await api.getAllProducts();
});
// Use with provider integration
JetPaginator.list<Product>(
fetchPage: (pageKey) => api.getProducts(skip: pageKey, limit: 20),
parseResponse: (response, pageKey) => PageInfo(
items: (response['products'] as List)
.map((json) => Product.fromJson(json))
.toList(),
nextPageKey: response['skip'] + response['limit'] < response['total']
? response['skip'] + response['limit']
: null,
),
itemBuilder: (product, index) => ProductCard(product: product),
provider: allProductsProvider, // Enable Riverpod integration
)
With Family Providers:
// Family providers can't be passed directly, but you can still manually invalidate
JetPaginator.list<Product>(
fetchPage: (pageKey) => ref.read(productsProvider(pageKey).future),
parseResponse: (response, pageKey) => PageInfo(...),
itemBuilder: (product, index) => ProductCard(product: product),
onRefresh: () => ref.invalidate(productsProvider), // Manual invalidate
)
Cursor-based API:
JetPaginator.list<Post>(
fetchPage: (cursor) => api.getPosts(cursor: cursor, limit: 20),
parseResponse: (response, currentCursor) => PageInfo(
items: response.data.map((json) => Post.fromJson(json)).toList(),
nextPageKey: response.pagination?.nextCursor,
isLastPage: !response.pagination?.hasMore,
),
itemBuilder: (post, index) => PostCard(post: post),
)
Custom Refresh Indicator:
JetPaginator.list<Product>(
fetchPage: (pageKey) => api.getProducts(skip: pageKey, limit: 20),
parseResponse: (response, pageKey) => PageInfo(...),
itemBuilder: (product, index) => ProductCard(product: product),
// Simple color customization
refreshIndicatorColor: Colors.green,
refreshIndicatorBackgroundColor: Colors.grey[100],
refreshIndicatorStrokeWidth: 3.0,
refreshIndicatorDisplacement: 50.0,
)
Fully Custom Refresh Indicator:
JetPaginator.list<Product>(
fetchPage: (pageKey) => api.getProducts(skip: pageKey, limit: 20),
parseResponse: (response, pageKey) => PageInfo(...),
itemBuilder: (product, index) => ProductCard(product: product),
refreshIndicatorBuilder: (context, controller) {
return AnimatedContainer(
duration: Duration(milliseconds: 300),
child: Icon(
Icons.refresh,
color: controller.state.isLoading ? Colors.blue : Colors.grey,
size: 24 + (controller.value * 12), // Animated size
),
);
},
)
Page-based API:
JetPaginator.list<User>(
fetchPage: (page) => api.getUsers(page: page, size: 15),
parseResponse: (response, currentPage) => PageInfo(
items: response.content.map((json) => User.fromJson(json)).toList(),
nextPageKey: response.hasNext ? (currentPage as int) + 1 : null,
totalItems: response.totalElements,
),
itemBuilder: (user, index) => UserCard(user: user),
)
Properties
- hashCode → int
-
The hash code for this object.
no setterinherited
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
Methods
-
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
toString(
) → String -
A string representation of this object.
inherited
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited
Static Methods
-
grid<
T, TResponse> ({required Future< TResponse> fetchPage(dynamic pageKey), required PageInfo<T> parseResponse(TResponse response, dynamic currentPageKey), required Widget itemBuilder(T item, int index), required int crossAxisCount, dynamic firstPageKey = 0, bool enablePullToRefresh = true, Object? provider, double crossAxisSpacing = 8.0, double mainAxisSpacing = 8.0, double childAspectRatio = 1.0, Widget refreshIndicatorBuilder(BuildContext context, IndicatorController controller)?, Color? refreshIndicatorColor, Color? refreshIndicatorBackgroundColor, double? refreshIndicatorStrokeWidth, double? refreshIndicatorDisplacement, Widget? loadingIndicator, Widget errorIndicator(Object error, WidgetRef ref)?, Widget fetchMoreErrorIndicator(Object error, WidgetRef ref)?, Widget? noItemsIndicator, Widget? noMoreItemsIndicator, EdgeInsets? padding, bool shrinkWrap = false, ScrollPhysics? physics, VoidCallback? onRetry, VoidCallback? onRefresh, VoidCallback? onNoItemsActionTap, String? noItemsTitle, String? noItemsMessage, String? noItemsActionTitle, String? noMoreItemsTitle, String? noMoreItemsMessage, String? noMoreItemsActionTitle, int itemsThresholdToTriggerLoad = 3}) → Widget - Creates an infinite scroll grid that works with any API format
-
list<
T, TResponse> ({required Future< TResponse> fetchPage(dynamic pageKey), required PageInfo<T> parseResponse(TResponse response, dynamic currentPageKey), required Widget itemBuilder(T item, int index), dynamic firstPageKey = 0, bool enablePullToRefresh = true, Object? provider, Widget refreshIndicatorBuilder(BuildContext context, IndicatorController controller)?, Color? refreshIndicatorColor, Color? refreshIndicatorBackgroundColor, double? refreshIndicatorStrokeWidth, double? refreshIndicatorDisplacement, Widget? loadingIndicator, Widget errorIndicator(Object error, WidgetRef ref)?, Widget fetchMoreErrorIndicator(Object error, WidgetRef ref)?, Widget? noItemsIndicator, Widget? noMoreItemsIndicator, EdgeInsets? padding, bool shrinkWrap = false, ScrollPhysics? physics, VoidCallback? onRetry, VoidCallback? onRefresh, VoidCallback? onNoItemsActionTap, String? noItemsTitle, String? noItemsMessage, String? noItemsActionTitle, String? noMoreItemsTitle, String? noMoreItemsMessage, String? noMoreItemsActionTitle, int itemsThresholdToTriggerLoad = 3}) → Widget - Creates an infinite scroll list that works with any API format