Riverpod Patterns
Best practices and common patterns for using Fasq with Riverpod to build maintainable and performant applications.
Provider Organization
Section titled “Provider Organization”Group Related Providers
Section titled “Group Related Providers”Organize providers by feature or domain. Instead of .family, use functions to pass parameters:
final usersProvider = queryProvider<List<User>>( 'users'.toQueryKey(), () => api.fetchUsers(),);
final userProvider = (String userId) => queryProvider<User>( ['user', userId].toQueryKey(), () => api.fetchUser(userId),);
final createUserProvider = mutationProvider<User, Map<String, dynamic>>( (data) => api.createUser(data),);
final updateUserProvider = (String userId) => mutationProvider<User, Map<String, dynamic>>( (data) => api.updateUser(userId, data),);State Management Patterns
Section titled “State Management Patterns”Derived State
Section titled “Derived State”Create computed state from query results using Riverpod’s Provider:
final activeUsersProvider = Provider<List<User>>((ref) { // Watch the query provider final users = ref.watch(usersProvider).value ?? [];
// Return derived data return users.where((user) => user.isActive).toList();});
final userCountProvider = Provider<int>((ref) { return ref.watch(usersProvider).value?.length ?? 0;});Side Effects & Mutations
Section titled “Side Effects & Mutations”Global Error Handling
Section titled “Global Error Handling”Use a dedicated provider or utility for consistent error feedback:
final createUserProvider = mutationProvider<User, String>( (name) => api.createUser(name), options: MutationOptions( onError: (error, name) { // Access global notification service or context showGlobalError('Failed to create user "$name": $error'); }, ),);
// In your widget:ElevatedButton( onPressed: () => ref.read(createUserProvider.notifier).mutate('John Doe'), child: Text('Create'),)Cache Invalidation Strategies
Section titled “Cache Invalidation Strategies”Centralize cache invalidation after mutations to keep your UI in sync:
final updateUserProvider = (String userId) => mutationProvider<User, String>( (newName) => api.updateUser(userId, newName), options: MutationOptions( onSuccess: (user, newName) { // 1. Invalidate specific user query ref.invalidate(userProvider(userId));
// 2. Invalidate users list ref.invalidate(usersProvider);
// 3. Optional: manually update cache for instant feedback // ref.read(fasqClientProvider).setQueryData(['user', userId].toQueryKey(), user); }, ),);Form Handling
Section titled “Form Handling”Integrate mutations with form state:
class UserForm extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final mutation = ref.watch(createUserProvider); final nameController = useTextEditingController();
return Column( children: [ TextField(controller: nameController),
ElevatedButton( // Disable button while loading onPressed: mutation.isLoading ? null : () => ref.read(createUserProvider.notifier).mutate(nameController.text), child: mutation.isLoading ? CircularProgressIndicator() : Text('Submit'), ),
if (mutation.hasError) Text('Error: ${mutation.error}', style: TextStyle(color: Colors.red)), ], ); }}Repository Pattern
Section titled “Repository Pattern”For complex apps, wrap your API calls in a repository and inject it using Riverpod:
final userRepositoryProvider = Provider((ref) => UserRepository(api: api));
final usersProvider = queryProvider<List<User>>( 'users'.toQueryKey(), () { final repo = ref.watch(userRepositoryProvider); return repo.getAllUsers(); },);Performance Tips
Section titled “Performance Tips”- Watch Granularly: Use
.selectif you only need a specific property of the data to avoid unnecessary rebuilds. - Handle Loading Gracefully: Use
AsyncValue.whento ensure users see a loading state while data is being fetched. - Background Sync: Mention that
AsyncValueholds previous data during background refetching (stale-while-revalidate), making the app feel faster. - Dispose Unused Queries:
queryProviderusesAutoDisposeby default, so it cleans up after itself when the last widget stops watching.
Next Steps
Section titled “Next Steps”queryProvider- Standard data fetchingmutationProvider- Handling server-side updates- Examples - Full application examples