🧪 Skills
Riverpod
Flutter state management with Riverpod - declarative, type-safe, and code-generated providers. Use when building Flutter apps that need reactive state manage...
v1.0.0
Description
name: riverpod description: Flutter state management with Riverpod - declarative, type-safe, and code-generated providers. Use when building Flutter apps that need reactive state management, dependency injection, and testable business logic. Covers ProviderNotifier, AsyncNotifier, StreamProvider, Family modifiers, and code generation with @riverpod.
Riverpod State Management
Riverpod is a declarative, type-safe state management solution for Flutter. It uses code generation for boilerplate reduction.
Core Concepts
1. Declarative State with @riverpod
Mark classes or functions with @riverpod for code generation:
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
}
2. Consuming State
Use ConsumerWidget or ConsumerStatefulWidget:
class CounterView extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
}
3. Provider Types
| Type | Use For | Syntax |
|---|---|---|
| StateProvider | Simple state | @riverpod class X extends _$X |
| AsyncNotifier | Async loading | @riverpod class X extends _$X with Future |
| StreamProvider | Real-time data | @riverpod Stream<T> func(Ref ref) |
| Family providers | Parameterized | @riverpod class X extends Family<X, Args> |
Code Patterns
Pattern 1: AsyncNotifier for API Calls
@riverpod
class UserController extends _$UserController {
@override
Future<User> build(String userId) async {
final dio = ref.watch(dioProvider);
final response = await dio.get('/users/$userId');
return User.fromJson(response.data);
}
Future<void> updateUser(User user) async {
// Optimistic update
final previous = await future;
state = AsyncData(user);
try {
await ref.read(dioProvider).put('/users/${user.id}', user.toJson());
} catch (e) {
state = AsyncError(e, StackTrace.current);
}
}
}
Pattern 2: Combining Providers
@riverpod
List<Todo> filteredTodos(Ref ref) {
final todos = ref.watch(todosProvider);
final filter = ref.watch(filterProvider);
return switch (filter) {
Filter.all => todos,
Filter.completed => todos.where((t) => t.completed).toList(),
Filter.uncompleted => todos.where((t) => !t.completed).toList(),
};
}
Pattern 3: Dependency Injection
@riverpod
Dio dio(Ref ref) {
final baseUrl = ref.watch(baseUrlProvider);
return Dio(BaseOptions(baseUrl: baseUrl));
}
Pattern 4: AsyncValue Pattern Matching
class AsyncWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final data = ref.watch(asyncProvider);
return switch (data) {
AsyncData(:final value) => Text('$value'),
AsyncError(:final error, :final stackTrace) => ErrorWidget(error),
AsyncLoading() => const CircularProgressIndicator(),
};
}
}
Provider Modifiers
Family - Parameterized Providers
@riverpod
Future<User> user(UserRef ref, String userId) async {
return await api.getUser(userId);
}
// Usage
ref.watch(userProvider('123'));
AutoDispose - Automatic Cleanup
@Riverpod(keepAlive: false)
Future<User> temporaryUser(TemporaryUserRef ref, String id) async {
ref.onDispose(() {
// Cleanup logic
});
return await api.getUser(id);
}
Widget Patterns
ConsumerWidget Pattern
class MyWidget extends ConsumerWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final value = ref.watch(myProvider);
return Text('$value');
}
}
ConsumerStatefulWidget Pattern
class MyPage extends ConsumerStatefulWidget {
const MyPage({super.key});
@override
ConsumerState<MyPage> createState() => _MyPageState();
}
class _MyPageState extends ConsumerState<MyPage> {
@override
void initState() {
super.initState();
ref.read(myProvider.notifier).load();
}
@override
Widget build(BuildContext context) {
final state = ref.watch(myProvider);
return Scaffold(body: Text('$state'));
}
}
HookConsumerWidget (with flutter_hooks)
class HookWidget extends HookConsumerWidget {
const HookWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final controller = useTextController();
final count = ref.watch(counterProvider);
return TextField(controller: controller);
}
}
Code Generation Workflow
- Add annotation:
@riverpodor@Riverpod() - Define class extending
_$ClassNameor function - Run:
flutter pub run build_runner watch --delete-conflicting-outputs - Generated file:
.g.dartextension
Essential Commands
# Generate once
flutter pub run build_runner build --delete-conflicting-outputs
# Watch for changes (recommended during development)
flutter pub run build_runner watch --delete-conflicting-outputs
Testing
testWidgets('counter increments', (tester) async {
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(home: CounterView()),
),
);
expect(find.text('0'), findsOneWidget);
});
ref Methods
| Method | Use For |
|---|---|
| ref.watch(provider) | Rebuild when value changes |
| ref.read(provider) | One-time read, no rebuild |
| ref.listen(provider, cb) | Side effects on change |
| ref.refresh(provider) | Force reload |
| ref.invalidate(provider) | Mark as needing refresh |
Best Practices
See BEST_PRACTICES.md for detailed guidelines on:
- Provider architecture
- Avoiding common pitfalls
- Performance optimization
- Testing strategies
Reviews (0)
Sign in to write a review.
No reviews yet. Be the first to review!
Comments (0)
No comments yet. Be the first to share your thoughts!