Skip to content
Development
Skill

/riverpod

Use when setting up providers, combining requests, managing state disposal, passing arguments, performing side effects, or testing providers (Riverpod).

From plugin
flutter-ai-skills
63937 skills
Install
$ npx -y skills add evanca/flutter-ai-rules --skill riverpod --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/riverpod

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when setting up providers, combining requests, managing state disposal, passing arguments, performing side effects, or testing providers (Riverpod).

SKILL.md

riverpod.SKILL.md
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

void main() {
  runApp(const ProviderScope(child: MyApp()));
}
  • Wrap your app with `ProviderScope` directly in `runApp` — never inside `MyApp`.
  • Install and use `riverpod_lint` to enable IDE refactoring and enforce best practices.

---

2. Defining Providers

// Functional provider (codegen)
@riverpod
int example(Ref ref) => 0;

// FutureProvider (codegen)
@riverpod
Future<List<Todo>> todos(Ref ref) async {
  return ref.watch(repositoryProvider).fetchTodos();
}

// Notifier (codegen)
@riverpod
class TodosNotifier extends _$TodosNotifier {
  @override
  Future<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`, or `StreamProvider` based on the return type.
  • Use `ConsumerWidget` or `ConsumerStatefulWidget` instead of `StatelessWidget`/`StatefulWidget` when 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. |

// In a widget
class MyWidget extends ConsumerWidget {
  @override
  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 `ref.watch` inside callbacks, listeners, or Notifier methods.
  • Use `ref.read(yourNotifierProvider.notifier).method()` to call Notifier methods from the UI.
  • Check `context.mounted` before using `ref` after an `await` in async callbacks.

---

4. Combining Providers

@riverpod
Future<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)

@riverpod
Future<Todo> todo(Ref ref, String id) async {
  return ref.watch(repositoryProvider).fetchTodo(id);
}

// Usage
final todo = ref.watch(todoProvider('some-id'));
  • Always enable `autoDispose` for parameterized providers to prevent memory leaks.
  • Use `Dart 3 records` or code generation for multiple parameters — they naturally override `==`.
  • Avoid passing plain `List` or `Map` as parameters (no `==` override); use `const` collections, records, or classes with proper equality.
  • Use the `provider_parameters` lint rule from `riverpod_lint` to 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 `.autoDispose` to enable disposal.
  • State is always destroyed when a provider is recomputed.
// keepAlive with timer
ref.onCancel(() {
  final link = ref.keepAlive();
  Timer(const Duration(minutes: 5), link.close);
});
  • Use `ref.onDispose` for cleanup; do not trigger side effects or modify providers inside it.
  • Use `ref.invalidate(provider)` to force destruction; use `ref.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:

// 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 `main()`) for consistent test behavior.
  • Use `AsyncValue.requireValue` to read data directly and throw clearly if not ready.

---

8. Performing Side Effects

@riverpod
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 `ref.read` (not `ref.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

class MyObserver extends ProviderObserver {
  @override
  void didUpdateProvider(ProviderObserverContext context, Object? previousValue, Object? newValue) {
    print('[${context.provider}] updated: $previousValue → $newValue');
  }

  @override
  void providerDidFail(ProviderObserverContext context, Object error, StackTrace stackTrace) {
    // Report to error service
  }
}

runApp(ProviderScope(observers: [MyObserver()], child: MyApp()));

---

10. Testing

// Unit test
final container = ProviderContainer(
  overrides: [reposit
Read more
Ships withflutter-ai-skills

36 Flutter and Dart skills your coding agent loads by itself, sourced only from official documentation. A skill is a folder with a SKILL.md file.

Get the whole plugin
Stats
639
Stars
66
Forks
Active
Maintenance
Shell
Language
MIT
License
2d ago
Last commit
1y ago
Created

Repo: evanca/flutter-ai-rules

Other skills on flutter-ai-skills.