Pagination
Implement pagination with Fasq using query keys that include page parameters.
Basic Pagination
Section titled “Basic Pagination”Use page numbers in your query keys to enable pagination:
class PostsScreen extends StatefulWidget { @override _PostsScreenState createState() => _PostsScreenState();}
class _PostsScreenState extends State<PostsScreen> { int currentPage = 1; final int pageSize = 10;
@override Widget build(BuildContext context) { return QueryBuilder<List<Post>>( queryKey: 'posts:page:$currentPage:size:$pageSize', queryFn: () => api.fetchPosts(page: currentPage, size: pageSize), builder: (context, state) { if (state.isLoading) return CircularProgressIndicator(); if (state.hasError) return Text('Error: ${state.error}'); if (state.hasData) { return Column( children: [ Expanded( child: ListView.builder( itemCount: state.data!.length, itemBuilder: (context, index) => PostTile(state.data![index]), ), ), PaginationControls( currentPage: currentPage, onPageChanged: (page) => setState(() => currentPage = page), ), ], ); } return SizedBox(); }, ); }}Pagination Controls
Section titled “Pagination Controls”Create reusable pagination controls:
class PaginationControls extends StatelessWidget { final int currentPage; final Function(int) onPageChanged; final bool hasNextPage; final bool hasPreviousPage;
const PaginationControls({ required this.currentPage, required this.onPageChanged, this.hasNextPage = true, this.hasPreviousPage = true, });
@override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( onPressed: hasPreviousPage ? () => onPageChanged(currentPage - 1) : null, icon: Icon(Icons.chevron_left), ), Text('Page $currentPage'), IconButton( onPressed: hasNextPage ? () => onPageChanged(currentPage + 1) : null, icon: Icon(Icons.chevron_right), ), ], ); }}Prefetching Next Page
Section titled “Prefetching Next Page”Prefetch the next page for better user experience:
class PostsScreen extends StatefulWidget { @override _PostsScreenState createState() => _PostsScreenState();}
class _PostsScreenState extends State<PostsScreen> { int currentPage = 1;
void _prefetchNextPage() { final nextPage = currentPage + 1; QueryClient().prefetchQuery( 'posts:page:$nextPage:size:10', () => api.fetchPosts(page: nextPage, size: 10), ); }
@override Widget build(BuildContext context) { return QueryBuilder<List<Post>>( queryKey: 'posts:page:$currentPage:size:10', queryFn: () => api.fetchPosts(page: currentPage, size: 10), builder: (context, state) { if (state.hasData) { // Prefetch next page when current page loads WidgetsBinding.instance.addPostFrameCallback((_) { _prefetchNextPage(); }); }
return buildUI(state); }, ); }}Using Hooks Adapter
Section titled “Using Hooks Adapter”With the hooks adapter, pagination becomes more declarative:
class PostsScreen extends HookWidget { @override Widget build(BuildContext context) { final currentPage = useState(1); final pageSize = 10;
final postsState = useQuery( 'posts:page:${currentPage.value}:size:$pageSize', () => api.fetchPosts(page: currentPage.value, size: pageSize), );
return Column( children: [ if (postsState.isLoading) CircularProgressIndicator(), if (postsState.hasData) PostsList(postsState.data!), PaginationControls( currentPage: currentPage.value, onPageChanged: (page) => currentPage.value = page, ), ], ); }}Using Riverpod Adapter
Section titled “Using Riverpod Adapter”With Riverpod, create a family provider for pagination:
final postsProvider = queryProvider.family<List<Post>, int>( (page) => 'posts:page:$page:size:10', (page) => api.fetchPosts(page: page, size: 10),);
class PostsScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final currentPage = useState(1); final postsState = ref.watch(postsProvider(currentPage.value));
return Column( children: [ if (postsState.isLoading) CircularProgressIndicator(), if (postsState.hasData) PostsList(postsState.data!), PaginationControls( currentPage: currentPage.value, onPageChanged: (page) => currentPage.value = page, ), ], ); }}Cache Management
Section titled “Cache Management”Manage cache for paginated data:
class PostsScreen extends StatefulWidget { @override _PostsScreenState createState() => _PostsScreenState();}
class _PostsScreenState extends State<PostsScreen> { int currentPage = 1;
void _clearCache() { // Clear all posts cache QueryClient().invalidateQueriesWithPrefix('posts:'); }
void _prefetchPage(int page) { QueryClient().prefetchQuery( 'posts:page:$page:size:10', () => api.fetchPosts(page: page, size: 10), ); }
@override Widget build(BuildContext context) { return Column( children: [ ElevatedButton( onPressed: _clearCache, child: Text('Clear Cache'), ), QueryBuilder<List<Post>>( queryKey: 'posts:page:$currentPage:size:10', queryFn: () => api.fetchPosts(page: currentPage, size: 10), builder: (context, state) => buildUI(state), ), ], ); }}Performance Tips
Section titled “Performance Tips”- Use descriptive query keys - Include page and size parameters
- Prefetch adjacent pages - Improve perceived performance
- Configure cache time - Keep frequently accessed pages cached
- Invalidate on data changes - Clear cache when data is updated
- Use appropriate page sizes - Balance between requests and memory
Next Steps
Section titled “Next Steps”- Infinite Scroll - Load more data as user scrolls
- Prefetching - Advanced prefetching strategies
- Cache Invalidation - Managing cache lifecycle
- Dependent Queries - Sequencing data fetches