riverpod
Original:🇺🇸 English
Translated
Uses Riverpod for state management in Flutter/Dart. Use when setting up providers, combining requests, managing state disposal, passing arguments, performing side effects, testing providers, or applying Riverpod best practices.
39installs
Sourceevanca/flutter-ai-rules
Added on
NPX Install
npx skill4agent add evanca/flutter-ai-rules riverpodTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →Riverpod Skill
This skill defines how to correctly use Riverpod for state management in Flutter and Dart applications.
1. Setup
dart
void main() {
runApp(const ProviderScope(child: MyApp()));
}- Wrap your app with directly in
ProviderScope— never insiderunApp.MyApp - Install and use to enable IDE refactoring and enforce best practices.
riverpod_lint
2. Defining Providers
dart
// Functional provider (codegen)
int example(Ref ref) => 0;
// FutureProvider (codegen)
Future<List<Todo>> todos(Ref ref) async {
return ref.watch(repositoryProvider).fetchTodos();
}
// Notifier (codegen)
class TodosNotifier extends _$TodosNotifier {
Future<List<Todo>> build() async {
return ref.watch(repositoryProvider).fetchTodos();
}
Future<void> addTodo(Todo todo) async { ... }
}- Define all providers as top-level variables.
final - Use ,
Provider, orFutureProviderbased on the return type.StreamProvider - Use or
ConsumerWidgetinstead ofConsumerStatefulWidget/StatelessWidgetwhen accessing providers.StatefulWidget
3. Using Ref
| Method | Use for |
|---|---|
| Reactively listen — rebuilds when value changes. Use during build phase only. |
| One-time access — use in callbacks/Notifier methods, not in build. |
| Imperative subscription — prefer |
| Cleanup when provider state is destroyed. |
dart
// In a widget
class MyWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final value = ref.watch(myProvider);
return Text('$value');
}
}
// Cleanup in a provider
final provider = StreamProvider<int>((ref) {
final controller = StreamController<int>();
ref.onDispose(controller.close);
return controller.stream;
});- Never call inside callbacks, listeners, or Notifier methods.
ref.watch - Use to call Notifier methods from the UI.
ref.read(yourNotifierProvider.notifier).method() - Check before using
context.mountedafter anrefin async callbacks.await
4. Combining Providers
dart
Future<String> userGreeting(Ref ref) async {
final user = await ref.watch(userProvider.future);
return 'Hello, ${user.name}!';
}- Use to await an async provider's resolved value.
ref.watch(asyncProvider.future) - Providers only execute once and cache the result — multiple widgets listening to the same provider share one computation.
5. Passing Arguments (Families)
dart
Future<Todo> todo(Ref ref, String id) async {
return ref.watch(repositoryProvider).fetchTodo(id);
}
// Usage
final todo = ref.watch(todoProvider('some-id'));- Always enable for parameterized providers to prevent memory leaks.
autoDispose - Use or code generation for multiple parameters — they naturally override
Dart 3 records.== - Avoid passing plain or
Listas parameters (noMapoverride); use==collections, records, or classes with proper equality.const - Use the lint rule from
provider_parametersto catch equality mistakes.riverpod_lint
6. Auto Dispose & State Lifecycle
- With codegen: state is destroyed by default when no longer listened to. Opt out with .
keepAlive: true - Without codegen: state is kept alive by default. Use to enable disposal.
.autoDispose - State is always destroyed when a provider is recomputed.
dart
// keepAlive with timer
ref.onCancel(() {
final link = ref.keepAlive();
Timer(const Duration(minutes: 5), link.close);
});- Use for cleanup; do not trigger side effects or modify providers inside it.
ref.onDispose - Use to force destruction; use
ref.invalidate(provider)from within the provider.ref.invalidateSelf() - Use to invalidate and immediately read the new value — always use the return value.
ref.refresh(provider)
7. Eager Initialization
Providers are lazy by default. To eagerly initialize:
dart
// In MyApp or a dedicated widget under ProviderScope:
Consumer(
builder: (context, ref, _) {
ref.watch(myEagerProvider); // forces initialization
return const MyApp();
},
)- Place eager initialization in a public widget (not ) for consistent test behavior.
main() - Use to read data directly and throw clearly if not ready.
AsyncValue.requireValue
8. Performing Side Effects
dart
class TodosNotifier extends _$TodosNotifier {
Future<void> addTodo(Todo todo) async {
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
await ref.read(repositoryProvider).addTodo(todo);
return [...?state.value, todo];
});
}
}
// In UI:
ElevatedButton(
onPressed: () => ref.read(todosNotifierProvider.notifier).addTodo(todo),
child: const Text('Add'),
)- Use (not
ref.read) in event handlers.ref.watch - After a side effect, update state by: setting it directly, calling , or manually updating the cache.
ref.invalidateSelf() - Always handle loading and error states in the UI.
- Do not perform side effects in provider constructors or build methods.
9. Provider Observers
dart
class MyObserver extends ProviderObserver {
void didUpdateProvider(ProviderObserverContext context, Object? previousValue, Object? newValue) {
print('[${context.provider}] updated: $previousValue → $newValue');
}
void providerDidFail(ProviderObserverContext context, Object error, StackTrace stackTrace) {
// Report to error service
}
}
runApp(ProviderScope(observers: [MyObserver()], child: MyApp()));10. Testing
dart
// Unit test
final container = ProviderContainer(
overrides: [repositoryProvider.overrideWith((_) => FakeRepository())],
);
addTearDown(container.dispose);
expect(await container.read(todosProvider.future), isNotEmpty);
// Widget test
await tester.pumpWidget(
ProviderScope(
overrides: [repositoryProvider.overrideWith((_) => FakeRepository())],
child: const MyApp(),
),
);- Create a new or
ProviderContainerfor each test — never share state between tests.ProviderScope - Use over
container.listenforcontainer.readproviders to keep state alive during the test.autoDispose - Use to inject mocks or fakes.
overrides - Prefer mocking dependencies (repositories) rather than Notifiers directly.
- If you must mock a Notifier, subclass the original — don't use or
implements.with Mock - Place Notifier mocks in the same file as the Notifier if using code generation.
- Obtain the container in widget tests with .
ProviderScope.containerOf(tester.element(...))