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
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
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
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
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
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
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
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
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
- QueryCubit - Learn about basic queries
- MutationCubit - Learn about mutations
- Examples - Complete working examples
Last updated on