Flutter Riverpod Expert
Expert knowledge in Flutter Riverpod state management (2025 best practices). Use when working with Riverpod, Flutter state management, AsyncNotifier, provider types, code generation with riverpod_generator, state synchronization, or when the user mentions data fetching, mutations, reactive state, performance optimization, or testing in Flutter apps. Covers AsyncNotifierProvider patterns, repository architecture, autoDispose, family providers, and common anti-patterns to avoid.
- Skill ID
- juparave/dotfiles/flutter-riverpod-expert
- Publisher
- juparave
- Repository
- dotfiles
- Installs
- 0
- Files
- 1
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
juparave/dotfiles/flutter-riverpod-expertInstalls these files- SKILL.md
What this skill tells the agent
Flutter Riverpod Expert - 2025 Best Practices
You have expert knowledge in Flutter Riverpod state management following 2025 best practices. When the user is working with Riverpod or Flutter state management, apply these patterns and guidelines.
When to Use This Skill
Activate this expertise when the user mentions:
- Riverpod, providers, state management, or StateNotifier
- AsyncNotifier, FutureProvider, StreamProvider, NotifierProvider
- Code generation with riverpod_generator or build_runner
- Data fetching, API integration, mutations, or reactive state
- State synchronization, caching, autoDispose, or memory management
- Provider testing, dependency injection, or repository patterns
- Performance issues with rebuilds, provider selection, or optimization
- Migration from old Riverpod patterns to modern approaches
Core Principles (2025)
- Code Generation is STRONGLY RECOMMENDED - Use
@riverpodannotations andriverpod_generator - AsyncNotifierProvider is PREFERRED for async state (replaces FutureProvider/StreamProvider for consistency)
- AutoDispose by Default - Codegen makes providers auto-dispose automatically
- Repository Pattern - Separate data layer from state management
- Performance First - Use
select()to optimize rebuilds
Provider Selection Guide
Quick Decision Tree
Immutable/Computed Values - Use Provider:
@riverpod
String apiKey(Ref ref) => 'YOUR_API_KEY';
@riverpod
int totalPrice(Ref ref) {
final cart = ref.watch(cartProvider);
return cart.items.fold(0, (sum, item) => sum + item.price);
}Simple Synchronous State - Use NotifierProvider:
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
void decrement() => state = max(0, state - 1);
}Async Data with Mutations (PREFERRED 2025) - Use AsyncNotifierProvider:
@riverpod
class TodoList extends _$TodoList {
@override
Future<List<Todo>> build() async {
final repo = ref.watch(todoRepositoryProvider);
return repo.fetchTodos();
}
Future<void> addTodo(String title) async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
final repo = ref.read(todoRepositoryProvider);
await repo.createTodo(title);
return repo.fetchTodos();
});
}
Future<void> deleteTodo(String id) async {
// Optimistic update
state = AsyncData(state.value!.where((t) => t.id != id).toList());
try {
await ref.read(todoRepositoryProvider).deleteTodo(id);
} catch (e) {
ref.invalidateSelf(); // Rollback on error
}
}
}Real-time Streams Only - Use StreamProvider:
@riverpod
Stream<User?> authState(Ref ref) {
return FirebaseAuth.instance.authStateChanges();
}Key Rule: Prefer AsyncNotifierProvider over FutureProvider/StreamProvider for better consistency and mutation support.
Code Generation Setup
Dependencies (pubspec.yaml)
dependencies:
flutter_riverpod: ^2.5.0
riverpod_annotation: ^2.3.0
dev_dependencies:
build_runner: ^2.4.0
riverpod_generator: ^2.4.0
custom_lint: ^0.6.0
riverpod_lint: ^2.3.0File Template
Every provider file needs:
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'filename.g.dart'; // REQUIRED
@riverpod
class MyProvider extends _$MyProvider {
@override
Future<Data> build() async => fetchData();
}Run Generator
# Watch mode (RECOMMENDED during development)
dart run build_runner watch -d
# One-time generation
dart run build_runner build --delete-conflicting-outputsPerformance Optimization Patterns
Use ref.select() for Specific Fields
// ❌ BAD: Rebuilds on ANY product change
final product = ref.watch(productProvider);
return Text('\$${product.price}');
// ✅ GOOD: Only rebuilds when price changes
final price = ref.watch(productProvider.select((p) => p.price));
return Text('\$$price');ref.watch() vs ref.select() vs ref.read() vs ref.listen()
ref.watch() - Subscribe to changes (use in build):
@override
Widget build(BuildContext context, WidgetRef ref) {
final todos = ref.watch(todoListProvider);
return ListView(...);
}ref.select() - Subscribe to specific property (optimize rebuilds):
final count = ref.watch(todoListProvider.select((todos) => todos.length));
final isAdult = ref.watch(personProvider.select((p) => p.age >= 18));ref.read() - One-time read with NO subscription (event handlers only):
onPressed: () {
ref.read(todoListProvider.notifier).addTodo('New task');
}
// ❌ NEVER use read() in build to "optimize" - it won't rebuild!ref.listen() - Side effects (navigation, snackbars, logging):
ref.listen<AsyncValue<List<Todo>>>(
todoListProvider,
(previous, next) {
next.whenOrNull(
error: (error, stack) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $error')),
);
},
);
},
);Avoid Watching in Loops
// ❌ BAD: Causes performance issues
ListView.builder(
itemBuilder: (context, index) {
final todo = ref.watch(todoProvider(ids[index])); // DON'T!
return ListTile(...);
},
);
// ✅ GOOD: Separate widget for each item
class TodoItem extends ConsumerWidget {
const TodoItem({required this.todoId});
final String todoId;
@override
Widget build(BuildContext context, WidgetRef ref) {
final todo = ref.watch(todoProvider(todoId));
return ListTile(title: Text(todo.title));
}
}
class TodoList extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final ids = ref.watch(todoIdsProvider);
return ListView.builder(
itemCount: ids.length,
itemBuilder: (context, index) => TodoItem(todoId: ids[index]),
);
}
}Create Derived Providers
// ✅ GOOD: Separate provider for computed state
@riverpod
List<Todo> filteredSortedTodos(Ref ref) {
final todos = ref.watch(todoListProvider);
final filter = ref.watch(filterProvider);
final sortOrder = ref.watch(sortOrderProvider);
final filtered = todos.where((t) => t.matches(filter)).toList();
return filtered..sort(sortOrder.comparator);
}Repository Pattern Architecture
3-Layer Architecture
1. Data Layer - Repository:
@riverpod
TodoRepository todoRepository(Ref ref) {
return TodoRepository(dio: ref.watch(dioProvider));
}
class TodoRepository {
TodoRepository({required this.dio});
final Dio dio;
Future<List<Todo>> fetchTodos() async {
final response = await dio.get('/todos');
return (response.data as List)
.map((json) => Todo.fromJson(json))
.toList();
}
Future<Todo> createTodo(String title) async {
final response = await dio.post('/todos', data: {'title': title});
return Todo.fromJson(response.data);
}
Future<void> deleteTodo(String id) async {
await dio.delete('/todos/$id');
}
}2. Application Layer - State Management:
@riverpod
class TodoList extends _$TodoList {
@override
Future<List<Todo>> build() async {
final repository = ref.watch(todoRepositoryProvider);
return repository.fetchTodos();
}
Future<void> addTodo(String title) async {
final repository = ref.read(todoRepositoryProvider);
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
await repository.createTodo(title);
return repository.fetchTodos();
});
}
}3. Presentation Layer - UI:
class TodoListScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final todosAsync = ref.watch(todoListProvider);
return Scaffold(
appBar: AppBar(title: const Text('Todos')),
body: todosAsync.when(
data: (todos) => ListView.builder(
itemCount: todos.length,Use this skill in a project
Open any RiverX project, search the Skills panel for juparave/dotfiles/flutter-riverpod-expert, and install it in one click.
