riverpod
v1.0.0Flutter 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 gen...
Installation
Please help me install the skill `riverpod` from SkillHub official store.
npx skills add tobiasbentin/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