InfiniteQueryCubit
The InfiniteQueryCubit is an abstract base class for managing infinite/paginated queries. Extend it to create cubits that handle paginated data fetching with automatic page management.
Basic Usage
Section titled “Basic Usage”Extend InfiniteQueryCubit and implement the required getters:
class PostsInfiniteQueryCubit extends InfiniteQueryCubit<List<Post>, int> { @override String get key => 'posts';
@override Future<List<Post>> Function(int param) get queryFn => (page) => api.fetchPosts(page: page);
@override InfiniteQueryOptions<List<Post>, int>? get options => InfiniteQueryOptions( getNextPageParam: (pages, last) => pages.length + 1, );}
BlocProvider( create: (_) => PostsInfiniteQueryCubit(), child: BlocBuilder<PostsInfiniteQueryCubit, InfiniteQueryState<List<Post>, int>>( builder: (context, state) { final allPosts = state.pages.expand((p) => p.data ?? []).toList(); return ListView.builder( itemCount: allPosts.length, itemBuilder: (_, i) => PostItem(allPosts[i]), ); }, ),)Pagination Methods
Section titled “Pagination Methods”Use the provided pagination methods:
class PostsInfiniteQueryCubit extends InfiniteQueryCubit<List<Post>, int> { @override String get key => 'posts';
@override Future<List<Post>> Function(int param) get queryFn => (page) => api.fetchPosts(page: page);
@override InfiniteQueryOptions<List<Post>, int>? get options => InfiniteQueryOptions( getNextPageParam: (pages, last) => pages.length + 1, );}
class PostsScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: BlocProvider( create: (_) => PostsInfiniteQueryCubit(), child: BlocBuilder<PostsInfiniteQueryCubit, InfiniteQueryState<List<Post>, int>>( builder: (context, state) { final allPosts = state.pages.expand((p) => p.data ?? []).toList();
return Column( children: [ Expanded( child: ListView.builder( itemCount: allPosts.length, itemBuilder: (_, i) => PostItem(allPosts[i]), ), ), if (state.hasNextPage) ElevatedButton( onPressed: () { context.read<PostsInfiniteQueryCubit>().fetchNextPage(); }, child: Text('Load More'), ), ], ); }, ), ), ); }}Cursor-Based Pagination
Section titled “Cursor-Based Pagination”Use cursor-based pagination with string parameters:
class PostsCursorQueryCubit extends InfiniteQueryCubit<List<Post>, String?> { @override String get key => 'posts:cursor';
@override Future<List<Post>> Function(String? param) get queryFn => (cursor) => api.fetchPosts(cursor: cursor);
@override InfiniteQueryOptions<List<Post>, String?>? get options => InfiniteQueryOptions( getNextPageParam: (pages, last) { if (last == null || last.isEmpty) return null; return last.last.cursor; }, );}Bidirectional Pagination
Section titled “Bidirectional Pagination”Support both forward and backward pagination:
class PostsBidirectionalCubit extends InfiniteQueryCubit<List<Post>, int> { @override String get key => 'posts:bidirectional';
@override Future<List<Post>> Function(int param) get queryFn => (page) => api.fetchPosts(page: page);
@override InfiniteQueryOptions<List<Post>, int>? get options => InfiniteQueryOptions( getNextPageParam: (pages, last) => pages.length + 1, getPreviousPageParam: (pages, first) => pages.first.param > 1 ? pages.first.param - 1 : null, );}
class PostsBidirectionalScreen extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( create: (_) => PostsBidirectionalCubit(), child: BlocBuilder<PostsBidirectionalCubit, InfiniteQueryState<List<Post>, int>>( builder: (context, state) { final allPosts = state.pages.expand((p) => p.data ?? []).toList();
return Column( children: [ if (state.hasPreviousPage) ElevatedButton( onPressed: () { context.read<PostsBidirectionalCubit>().fetchPreviousPage(); }, child: Text('Load Previous'), ), Expanded( child: ListView.builder( itemCount: allPosts.length, itemBuilder: (_, i) => PostItem(allPosts[i]), ), ), if (state.hasNextPage) ElevatedButton( onPressed: () { context.read<PostsBidirectionalCubit>().fetchNextPage(); }, child: Text('Load More'), ), ], ); }, ), ); }}Refetching Specific Pages
Section titled “Refetching Specific Pages”Refetch a specific page by index:
class PostsRefetchCubit extends InfiniteQueryCubit<List<Post>, int> { @override String get key => 'posts';
@override Future<List<Post>> Function(int param) get queryFn => (page) => api.fetchPosts(page: page);
@override InfiniteQueryOptions<List<Post>, int>? get options => InfiniteQueryOptions( getNextPageParam: (pages, last) => pages.length + 1, );}
class PostsRefetchScreen extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( create: (_) => PostsRefetchCubit(), child: BlocBuilder<PostsRefetchCubit, InfiniteQueryState<List<Post>, int>>( builder: (context, state) { return Column( children: [ ElevatedButton( onPressed: () { context.read<PostsRefetchCubit>().refetchPage(0); }, child: Text('Refresh First Page'), ), Expanded( child: ListView.builder( itemCount: state.pages.length, itemBuilder: (context, index) { final page = state.pages[index]; return PageTile( pageNumber: index + 1, posts: page.data ?? [], isLoading: page.data == null && page.error == null, ); }, ), ), ], ); }, ), ); }}Resetting Pages
Section titled “Resetting Pages”Reset all pages to start fresh:
class PostsResetCubit extends InfiniteQueryCubit<List<Post>, int> { @override String get key => 'posts';
@override Future<List<Post>> Function(int param) get queryFn => (page) => api.fetchPosts(page: page);
@override InfiniteQueryOptions<List<Post>, int>? get options => InfiniteQueryOptions( getNextPageParam: (pages, last) => pages.length + 1, );}
class PostsResetScreen extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( create: (_) => PostsResetCubit(), child: BlocBuilder<PostsResetCubit, InfiniteQueryState<List<Post>, int>>( builder: (context, state) { return Column( children: [ ElevatedButton( onPressed: () { context.read<PostsResetCubit>().reset(); }, child: Text('Reset All Pages'), ), Expanded( child: ListView.builder( itemCount: state.pages.expand((p) => p.data ?? []).length, itemBuilder: (_, i) => PostItem(state.pages.expand((p) => p.data ?? []).toList()[i]), ), ), ], ); }, ), ); }}Page Limits
Section titled “Page Limits”Limit the number of pages kept in memory:
class PostsLimitedCubit extends InfiniteQueryCubit<List<Post>, int> { @override String get key => 'posts:limited';
@override Future<List<Post>> Function(int param) get queryFn => (page) => api.fetchPosts(page: page);
@override InfiniteQueryOptions<List<Post>, int>? get options => InfiniteQueryOptions( getNextPageParam: (pages, last) => pages.length + 1, maxPages: 5, );}Error Handling
Section titled “Error Handling”Handle errors per page:
class PostsErrorHandlingCubit extends InfiniteQueryCubit<List<Post>, int> { @override String get key => 'posts';
@override Future<List<Post>> Function(int param) get queryFn => (page) => api.fetchPosts(page: page);
@override InfiniteQueryOptions<List<Post>, int>? get options => InfiniteQueryOptions( getNextPageParam: (pages, last) => pages.length + 1, onError: (error) { print('Error fetching page: $error'); }, );}
class PostsErrorScreen extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( create: (_) => PostsErrorHandlingCubit(), child: BlocBuilder<PostsErrorHandlingCubit, InfiniteQueryState<List<Post>, int>>( builder: (context, state) { return ListView.builder( itemCount: state.pages.length, itemBuilder: (context, index) { final page = state.pages[index]; if (page.error != null) { return ErrorTile( error: page.error!, onRetry: () { context.read<PostsErrorHandlingCubit>().refetchPage(index); }, ); } return PostListTile(posts: page.data ?? []); }, ); }, ), ); }}Next Steps
Section titled “Next Steps”- QueryCubit - Learn about basic queries
- MutationCubit - Learn about mutations
- Examples - Complete working examples