riverpod_repo 5.1.1
riverpod_repo: ^5.1.1 copied to clipboard
Generate Riverpod FutureProvider, StreamProvider, and Provider declarations from repository interfaces using the repository pattern.
Repo Generator with Riverpod #
A build-time code generator that creates Riverpod providers from repository
interfaces. Each query method on your repository becomes a FutureProvider,
StreamProvider, or Provider — with optional .family and .autoDispose.
Mutations are never turned into providers (see Mutations vs queries).
Table of contents #
- Installation
- Quick start
- Generated output
- Using providers in the UI
- Mutations vs queries
- Keep-alive vs auto-dispose
- Annotations reference
- Provider naming rules
- Migration
- Example project
Installation #
Add to dev_dependencies (this is a code generator, not a runtime dependency):
dev_dependencies:
riverpod_repo: ^5.1.1
build_runner: ^2.15.0
riverpod_annotation: ^4.0.2
riverpod_generator: ^4.0.3
Your app still needs riverpod (or flutter_riverpod) at runtime.
The builder is applied automatically to packages that depend on riverpod_repo
(auto_apply: dependents). To customize exclusions, add a build.yaml:
targets:
$default:
builders:
riverpod_repo:
enabled: true
generate_for:
exclude:
- "**/*.repo.g.dart"
Run code generation:
dart run build_runner build
Quick start #
1. Define the repository #
// lib/data/book_repo.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod_repo/riverpod_repo.dart';
part 'book_repo.g.dart';
@Riverpod(keepAlive: true)
BookRepo bookRepo(Ref ref) => BookRepoImpl();
@riverpodRepo
abstract class BookRepo {
Future<List<Book>> getBooks({String search = ''});
Future<Book> getBook(String id);
Future<void> deleteBook(String id); // mutation — no provider generated
}
Import annotations from either:
package:riverpod_repo/riverpod_repo.dart(recommended), orpackage:riverpod_repo/annotations.dart
2. Implement the repository #
class BookRepoImpl implements BookRepo {
@override
Future<List<Book>> getBooks({String search = ''}) async { ... }
@override
Future<Book> getBook(String id) async { ... }
@override
Future<void> deleteBook(String id) async { ... }
}
3. Run the builder #
dart run build_runner build
This creates book_repo.repo.g.dart — a standalone library you import in
widgets or other providers:
import 'book_repo.repo.g.dart';
Note (5.0.0+): Repository providers are built with the public Riverpod API (
FutureProvider,StreamProvider,Provider). They are not processed byriverpod_generator. Only your hand-written@Riverpodfunction needs apart '*.g.dart';directive.
Generated output #
For each query method, the generator emits a provider that delegates to your repository instance:
// book_repo.repo.g.dart (generated)
import 'package:riverpod/riverpod.dart';
import 'book_repo.dart';
export 'book_repo.dart';
final bookRepoGetBooksProvider =
FutureProvider.autoDispose.family<List<Book>, ({String search})>((ref, arg) {
return ref.watch(bookRepoProvider).getBooks(search: arg.search);
});
final bookRepoGetBookProvider =
FutureProvider.autoDispose.family<Book, String>((ref, id) {
return ref.watch(bookRepoProvider).getBook(id);
});
/// Invalidates every generated query provider for BookRepo.
void invalidateBookRepoProviders(Ref ref) {
ref.invalidate(bookRepoGetBooksProvider);
ref.invalidate(bookRepoGetBookProvider);
}
Provider kind is inferred from the return type:
| Repository method return type | Generated provider |
|---|---|
Future<T> |
FutureProvider |
Stream<T> |
StreamProvider |
| anything else | Provider |
Parameters map to .family:
| Method signature | Family argument |
|---|---|
| no parameters | plain provider |
| one positional parameter | that type directly, e.g. family<T, String> |
| named and/or multiple parameters | Dart record, e.g. (search: 'dart') |
Using providers in the UI #
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'book_repo.repo.g.dart';
class BookList extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final books = ref.watch(
bookRepoGetBooksProvider((search: '')),
);
return books.when(
loading: () => const CircularProgressIndicator(),
error: (err, _) => Text('Error: $err'),
data: (list) => ListView(
children: [for (final b in list) Text(b.title)],
),
);
}
}
Mutations vs queries #
Providers are generated only for reads. A method is skipped when any of these apply:
| Rule | Example |
|---|---|
Returns Future<void> or Stream<void> |
Future<void> deleteBook(String id) |
Annotated with @repoMutation or @ignoreRepo |
@repoMutation Future<User> archiveUser() |
| Name starts with a mutation prefix | createOrder, updateProfile, deleteItem, setTheme, saveDraft, addComment, removeTag, putFile, postMessage |
Force a query that looks like a mutation #
@repoQuery
Future<DeletedItemsReport> deleteReport({DateTime? since});
Or with an explicit constructor when you need parameters on the annotation:
@RepoQueryAnnotation()
Future<DeletedItemsReport> deleteReport({DateTime? since});
Refresh reads after a mutation #
Call the generated invalidation helper (name: invalidate + repo class name +
Providers):
Future<void> onDeleteBook(WidgetRef ref, String id) async {
await ref.read(bookRepoProvider).deleteBook(id);
invalidateBookRepoProviders(ref);
}
Do not wrap mutations in FutureProvider — call repository methods directly
via ref.read(bookRepoProvider).deleteBook(...).
Keep-alive vs auto-dispose #
By default, generated providers use .autoDispose (cache cleared when nothing
listens).
Keep every query provider alive for the repository:
@RiverpodRepoAnnotation(keepAlive: true)
abstract class BookRepo { ... }
Override a single method back to auto-dispose:
@RiverpodRepoAnnotation(keepAlive: true)
abstract class BookRepo {
@RepoQueryAnnotation(keepAlive: false)
Future<List<Book>> searchBooks(String query);
}
Use
@RepoQueryAnnotation(keepAlive: false)(not@repoQuery(keepAlive: false)) — Dart only allows const constructor invocations on annotations when parameters are passed.
Annotations reference #
| Annotation | Target | Purpose |
|---|---|---|
@riverpodRepo |
class | Shorthand for @RiverpodRepoAnnotation() — enable generation |
@RiverpodRepoAnnotation({keepAlive}) |
class | Enable generation; default keepAlive for all queries |
@repoQuery |
method | Force provider generation (overrides mutation name heuristic) |
@RepoQueryAnnotation({keepAlive}) |
method | Force generation; optional per-method keepAlive |
@repoMutation / @ignoreRepo |
method | Skip provider generation |
Provider naming rules #
- Repository class name drives generated provider names:
BookRepo+getBooks→bookRepoGetBooksProvider - Your
@Riverpodfunction must return that class and use camelCase of the class name:BookRepo→bookRepo→bookRepoProvider - Import
*.repo.g.dartfor generated query providers; keeppart '*.g.dart'only on the source file for your@Riverpodprovider
Migration #
From 4.x → 5.0 #
- Output is now a single
*.repo.g.dartfile (no*.repo.dartpart file) - Remove
part 'foo.repo.dart';from source files - Import
foo.repo.g.dartwhere you use generated providers - Parameterized methods now use
.family(records for named/multi-arg methods)
From 5.0 → 5.1 #
- Mutation methods no longer get providers — add
@repoQueryif a read was skipped - Use
invalidate{RepoName}Providers(ref)after writes - Package is pure Dart (no Flutter SDK required in
pubspec.yaml) - Annotations are exported from
package:riverpod_repo/riverpod_repo.dart
Example project #
See example/ for a full repository with:
- class-level
keepAlive - per-method
@RepoQueryAnnotation(keepAlive: false) - a
Future<void>mutation (deleteBook) with no generated provider - custom model imports (
Student,Country,Hello)
Run generation from the repo root:
dart run build_runner build
License #
MIT — see LICENSE.
Support #
Questions: dilan@dilan.me