Skip to main content

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:

  1. Changes Paged<T> — instead of Future<PaginatedResponse<T>>, it becomes Future<ApiPagedResponse<T>> (your API type)
  2. Inlines the mapper — the mapper function is written directly into every .craft.dart file that has paged providers
  3. Wraps create() automatically — the generated buildPagedData() calls pagedMapper(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),
);
}
}
info

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.