Files
cursor_gen/flutter-cursor-templates/templates/rules/state-management/riverpod.mdc.tmpl
T

59 lines
1.9 KiB
Cheetah

---
description: "Riverpod conventions for {{PROJECT_NAME}}"
alwaysApply: true
---
# Riverpod Standards — {{PROJECT_NAME}}
## Provider types
| Type | Use case |
|------|----------|
| `AsyncNotifier` | Async state from {{BACKEND}} |
| `Notifier` | Synchronous derived/local UI state |
| `StreamNotifier` | Real-time subscriptions |
| `@riverpod` function | Simple computed/derived values |
## Code generation (mandatory)
```dart
// ✅ Always use @riverpod annotation
@riverpod
class AuthNotifier extends _$AuthNotifier {
@override
Future<User?> build() => ref.watch(authRepositoryProvider).currentUser();
Future<void> login(String email, String password) async {
state = const AsyncLoading();
state = await AsyncValue.guard(
() => ref.read(authRepositoryProvider).login(email, password),
);
}
}
// ❌ Never write manual provider declarations
final authProvider = StateNotifierProvider<AuthNotifier, AsyncValue<User?>>(
(ref) => AuthNotifier(),
); // DON'T DO THIS
```
## Rules
- `ref.watch()` inside `build()` ONLY — **never** `ref.read()` inside `build()`
- `ref.read(provider.notifier).method()` for mutations in gesture handlers
- `ref.invalidate(provider)` to refresh — never manually reset state to `AsyncLoading()`
- Family providers for parameterized data: `productDetailsProvider(productId)`
- Providers scoped at feature level; core providers in `lib/core/di/`
## AsyncValue in widgets
Every `AsyncValue` MUST handle all three states:
```dart
ref.watch(productsProvider).when(
data: (products) => ProductList(products: products),
loading: () => const ProductListShimmer(), // required
error: (e, _) => ErrorWidget(error: e), // required
)
```
## File locations in {{PROJECT_NAME}}
- `lib/features/[feature]/[feature]_provider.dart` (generated: `[feature]_provider.g.dart`)
- `lib/features/[feature]/[feature]_repository.dart`
- Run `dart run build_runner watch` during development