<< All versions
Skill v1.0.2
currentAutomated scan100/100evanca/flutter-ai-rules/riverpod
+1 new, ~1 modified
──Details
PublishedSeptember 25, 2026 at 09:43 PM
Content Hashsha256:5cbcf72c77a2e870...
Git SHA7b9cce235714
Bump Typepatch
──Files
Files (1 file, 6.9 KB)
SKILL.md6.9 KBactive
SKILL.md · 245 lines · 6.9 KB
version: "1.0.2" name: riverpod description: "Use when setting up providers, combining requests, managing state disposal, passing arguments, performing side effects, or testing providers (Riverpod)." license: MIT
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
ProviderScopedirectly inrunApp— never insideMyApp. - Install and use
riverpod_lintto enable IDE refactoring and enforce best practices.
2. Defining Providers
dart
// Functional provider (codegen)@riverpodint example(Ref ref) => 0;// FutureProvider (codegen)@riverpodFuture<List<Todo>> todos(Ref ref) async {return ref.watch(repositoryProvider).fetchTodos();}// Notifier (codegen)@riverpodclass TodosNotifier extends _$TodosNotifier {@overrideFuture<List<Todo>> build() async {return ref.watch(repositoryProvider).fetchTodos();}Future<void> addTodo(Todo todo) async { ... }}
- Define all providers as `final` top-level variables.
- Use
Provider,FutureProvider, orStreamProviderbased on the return type. - Use
ConsumerWidgetorConsumerStatefulWidgetinstead ofStatelessWidget/StatefulWidgetwhen accessing providers.
3. Using Ref
| Method | Use for | |
|---|---|---|
ref.watch | Reactively listen — rebuilds when value changes. Use during build phase only. | |
ref.read | One-time access — use in callbacks/Notifier methods, not in build. | |
ref.listen | Imperative subscription — prefer ref.watch where possible. | |
ref.onDispose | Cleanup when provider state is destroyed. |
dart
// In a widgetclass MyWidget extends ConsumerWidget {@overrideWidget build(BuildContext context, WidgetRef ref) {final value = ref.watch(myProvider);return Text('$value');}}// Cleanup in a providerfinal provider = StreamProvider<int>((ref) {final controller = StreamController<int>();ref.onDispose(controller.close);return controller.stream;});
- Never call
ref.watchinside callbacks, listeners, or Notifier methods. - Use
ref.read(yourNotifierProvider.notifier).method()to call Notifier methods from the UI. - Check
context.mountedbefore usingrefafter anawaitin async callbacks.
4. Combining Providers
dart
@riverpodFuture<String> userGreeting(Ref ref) async {final user = await ref.watch(userProvider.future);return 'Hello, ${user.name}!';}
- Use
ref.watch(asyncProvider.future)to await an async provider's resolved value. - Providers only execute once and cache the result — multiple widgets listening to the same provider share one computation.
5. Passing Arguments (Families)
dart
@riverpodFuture<Todo> todo(Ref ref, String id) async {return ref.watch(repositoryProvider).fetchTodo(id);}// Usagefinal todo = ref.watch(todoProvider('some-id'));
- Always enable
autoDisposefor parameterized providers to prevent memory leaks. - Use
Dart 3 recordsor code generation for multiple parameters — they naturally override==. - Avoid passing plain
ListorMapas parameters (no==override); useconstcollections, records, or classes with proper equality. - Use the
provider_parameterslint rule fromriverpod_lintto catch equality mistakes.
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
.autoDisposeto enable disposal. - State is always destroyed when a provider is recomputed.
dart
// keepAlive with timerref.onCancel(() {final link = ref.keepAlive();Timer(const Duration(minutes: 5), link.close);});
- Use
ref.onDisposefor cleanup; do not trigger side effects or modify providers inside it. - Use
ref.invalidate(provider)to force destruction; useref.invalidateSelf()from within the provider. - Use
ref.refresh(provider)to invalidate and immediately read the new value — always use the return value.
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 initializationreturn const MyApp();},)
- Place eager initialization in a public widget (not
main()) for consistent test behavior. - Use
AsyncValue.requireValueto read data directly and throw clearly if not ready.
8. Performing Side Effects
dart
@riverpodclass 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
ref.read(notref.watch) in event handlers. - After a side effect, update state by: setting it directly, calling
ref.invalidateSelf(), or manually updating the cache. - 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 {@overridevoid didUpdateProvider(ProviderObserverContext context, Object? previousValue, Object? newValue) {print('[${context.provider}] updated: $previousValue → $newValue');}@overridevoid providerDidFail(ProviderObserverContext context, Object error, StackTrace stackTrace) {// Report to error service}}runApp(ProviderScope(observers: [MyObserver()], child: MyApp()));
10. Testing
dart
// Unit testfinal container = ProviderContainer(overrides: [repositoryProvider.overrideWith((_) => FakeRepository())],);addTearDown(container.dispose);expect(await container.read(todosProvider.future), isNotEmpty);// Widget testawait tester.pumpWidget(ProviderScope(overrides: [repositoryProvider.overrideWith((_) => FakeRepository())],child: const MyApp(),),);
- Create a new
ProviderContainerorProviderScopefor each test — never share state between tests. - Use
container.listenovercontainer.readforautoDisposeproviders to keep state alive during the test. - Use
overridesto inject mocks or fakes. - Prefer mocking dependencies (repositories) rather than Notifiers directly.
- If you must mock a Notifier, subclass the original — don't use
implementsorwith 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(...)).