Create type-safe Riverpod providers with code generation. Use when implementing state management with AsyncNotifier, FutureProvider, or StreamProvider patterns.
Create type-safe Riverpod providers with code generation for state management.
You will create Riverpod providers using riverpod_annotation for:
Gather Requirements:
Create Provider File:
{package}/lib/{layer}/{name}_provider.dartriverpod_annotationImplement Provider:
@riverpod annotationref.watch() or ref.read()Run Code Generation:
cd frontend
dart run build_runner build --delete-conflicting-outputs
Verify Generated Files:
.g.dart file with provider definitionsQuality Checks:
make check-flutter
For simple values or synchronous computations:
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'config_provider.g.dart';
/// App configuration provider
@riverpod
AppConfig appConfig(Ref ref) {
return const AppConfig(
apiBaseUrl: 'https://api.example.com',
timeout: Duration(seconds: 30),
);
}
For asynchronous data fetching:
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:core_api/core_api.dart';
part 'user_provider.g.dart';
/// Fetch user profile by ID
@riverpod
Future<UserProfile> userProfile(Ref ref, String userId) async {
final apiClient = ref.watch(apiClientProvider);
return await apiClient.getUserProfile(userId);
}
/// Fetch all users
@riverpod
Future<List<User>> users(Ref ref) async {
final apiClient = ref.watch(apiClientProvider);
return await apiClient.getUsers();
}
For real-time data streams:
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
part 'notification_stream_provider.g.dart';
/// Stream of real-time notifications
@riverpod
Stream<List<Notification>> notificationStream(Ref ref, String userId) {
final supabase = Supabase.instance.client;
return supabase
.from('notifications')
.stream(primaryKey: ['id'])
.eq('user_id', userId)
.order('created_at', ascending: false)
.map((data) => data.map((json) => Notification.fromJson(json)).toList());
}
For mutable state with business logic:
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'counter_provider.g.dart';
/// Counter state notifier
@riverpod
class Counter extends _$Counter {
@override
int build() {
// Initial state
return 0;
}
void increment() {
state = state + 1;
}
void decrement() {
state = state - 1;
}
void reset() {
state = 0;
}
}
For async operations with mutable state:
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:core_api/core_api.dart';
part 'checkout_provider.g.dart';
/// Checkout creation notifier
@riverpod
class CheckoutCreator extends _$CheckoutCreator {
@override
FutureOr<Checkout?> build() {
// Initial state (no checkout)
return null;
}
Future<void> createCheckout({
required String productId,
required String productPriceId,
}) async {
// Set loading state
state = const AsyncValue.loading();
// Execute async operation
state = await AsyncValue.guard(() async {
final apiClient = ref.read(polarApiClientProvider);
return await apiClient.createCheckout(
productId: productId,
productPriceId: productPriceId,
);
});
}
void clear() {
state = const AsyncValue.data(null);
}
}
For providers that take parameters:
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'product_provider.g.dart';
/// Fetch product by ID
@riverpod
Future<Product> product(Ref ref, String productId) async {
final apiClient = ref.watch(apiClientProvider);
return await apiClient.getProduct(productId);
}
/// Usage:
/// ref.watch(productProvider('product-123'))
For caching data that should not be disposed:
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'auth_provider.g.dart';
/// Auth state provider (kept alive)
@Riverpod(keepAlive: true)
class AuthState extends _$AuthState {
@override
Future<User?> build() async {
final supabase = Supabase.instance.client;
return supabase.auth.currentUser;
}
Future<void> signIn(String email, String password) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
final response = await Supabase.instance.client.auth.signInWithPassword(
email: email,
password: password,
);
return response.user;
});
}
Future<void> signOut() async {
await Supabase.instance.client.auth.signOut();
state = const AsyncValue.data(null);
}
}
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class UserProfileScreen extends ConsumerWidget {
const UserProfileScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// Watch async provider
final profileAsync = ref.watch(userProfileProvider('user-123'));
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: profileAsync.when(
data: (profile) => ProfileContent(profile: profile),
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text('Error: $error'),
),
);
}
}
class CounterScreen extends ConsumerWidget {
const CounterScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// Watch counter state
final count = ref.watch(counterProvider);
return Column(
children: [
Text('Count: $count'),
ElevatedButton(
// Call notifier action
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Text('Increment'),
),
],
);
}
}
class CheckoutScreen extends ConsumerWidget {
const CheckoutScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// Listen to state changes
ref.listen<AsyncValue<Checkout?>>(
checkoutCreatorProvider,
(previous, next) {
next.when(
data: (checkout) {
if (checkout != null) {
// Navigate to checkout page
context.go('/checkout/${checkout.id}');
}
},
loading: () {},
error: (error, _) {
// Show error snackbar
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $error')),
);
},
);
},
);
// ...rest of widget
}
}
Provider Location:
apps/web/lib/features/{feature}/model/apps/web/lib/entities/{entity}/model/packages/core/{package}/lib/providers/Naming Conventions:
{name}_provider.dart{name} (e.g., userProfile){Name} (e.g., CheckoutCreator){name}Provider (e.g., userProfileProvider)Dependencies:
ref.watch() for reactive dependenciesref.read() for one-time reads (in methods)ref.listen() for side effectsState Management:
AsyncValue for async operations.when() for pattern matching async statesError Handling:
AsyncValue.guard() for async operations.when(error: ...)Performance:
keepAlive: true for expensive computationspart directiveref.read() in build methods (use ref.watch())If generation fails:
Check analysis_options.yaml:
errors:
undefined_class: ignore # For Riverpod Ref types
Regenerate:
cd frontend
dart run build_runner clean
dart run build_runner build --delete-conflicting-outputs
Check for errors:
make check-flutter
riverpod_annotation instead of manual provider creationpackages/core/polar/lib/providers/ for reference