Pagination
Fetching paginated data — infinite scroll lists, load-more buttons — usually means managing page numbers, loading states, and appending results manually. With riverpod_craft, you write one create(int page) method and the rest is generated.
Writing a Paged Provider
A paged provider is a @provider class where the create() method takes int page as its first parameter. This tells the code generator it's paginated.
class Notes extends _$Notes {
Paged<Note> create(int page, {required String? category}) async {
final response = await http.get(
Uri.parse('https://api.example.com/notes?page=$page&category=$category'),
);
return ApiPagedResponse.fromJson(
jsonDecode(response.body),
(json) => Note.fromJson(json),
);
}
}
The int page parameter is special — it won't appear in the provider's family args. Only the other parameters (like category) become family args:
// ✅ page is handled internally
ref.notesProvider(category: 'work');
// ❌ you don't pass page — the generated code manages it
ref.notesProvider(1, category: 'work'); // wrong!
Using in a Widget
With CraftPagedListView
The simplest way — pass the provider facade and the widget handles pagination automatically:
class NotesPage extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
return CraftPagedListView<Note>(
provider: ref.notesProvider(category: 'work'),
itemBuilder: (context, note, index) => ListTile(
title: Text(note.title),
subtitle: Text(note.body),
),
);
}
}
Global Mapper
Most APIs return their own pagination format — not PaginatedResponse. Instead of converting in every provider, you write one mapper function and the code generator applies it everywhere.
Step 1: Define your API model
class ApiPagedResponse<T> {
final List<T> items;
final int page;
final int totalItems;
final int totalPages;
final int perPage;
// ...
}
Step 2: Write the mapper function
Create a file (e.g., lib/paged_mapper.dart) with a function named pagedMapper:
import 'package:riverpod_craft/riverpod_craft.dart';
import 'models/api_paged_response.dart';
PaginatedResponse<T> pagedMapper<T>(ApiPagedResponse<T> data) {
return PaginatedResponse(
results: data.items,
currentPage: data.page,
total: data.totalItems,
lastPage: data.totalPages,
pageSize: data.perPage,
);
}
Step 3: Configure in riverpod_craft.yaml
paged_provider_mapper: lib/paged_mapper.dart
What happens
The code generator reads your mapper function and:
- Changes
Paged<T>— instead ofFuture<PaginatedResponse<T>>, it becomesFuture<ApiPagedResponse<T>>(your API type) - Inlines the mapper — the mapper function is written directly into every
.craft.dartfile that has paged providers - Wraps
create()automatically — the generatedbuildPagedData()callspagedMapper(await create(page, ...))
Your provider just returns the raw API response:
class Notes extends _$Notes {
Paged<Note> create(int page, {required String? category}) async {
final response = await http.get(
Uri.parse('https://api.example.com/notes?page=$page&category=$category'),
);
// Return your raw API type — the mapper handles conversion
return ApiPagedResponse.fromJson(
jsonDecode(response.body),
(json) => Note.fromJson(json),
);
}
}
You don't need to import the mapper file in your provider. The code generator copies the mapper function into the generated .craft.dart file automatically.
Next
To add side effects (delete, update) to your paged providers, see Command.
See the Annotations Reference for all pagination-related annotations.