SkillAgentSearch skills...

riverpod

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

Install / Use

npx skills add evanca/flutter-ai-rules --skill riverpod

Installs into whichever agent you are using.

About this skill
📄

SKILL.md

Installable skill definition

Quality Score

90/100

Supported Platforms

Universal

Our assessment of riverpod

riverpod scores 90/100 on our quality scale, 1161st of 4,634 Development & Engineering skills we index (top 26%).

Its SKILL.md is 6.9 KB long, well organised into 12 sections with 10 code examples: a thorough specification that gives an agent plenty to work with.

It has 646 GitHub stars, a meaningful sign that others use it.

Substance
29/30
Structure
20/20
Description
15/15
Adoption
12/20
Freshness
15/15

Maintenance, license and trust

  • The repository was last updated 19 days ago, so riverpod is actively maintained.
  • It is released under the MIT license, a permissive license that allows use, modification and commercial use with attribution.
  • Its trust signals score 100/100, with no cautions. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.

riverpod compared with similar skills

All 4 of these similar skills score higher than riverpod; compare them before choosing.

SkillScoreStarsUpdatedFormat
riverpod (this skill)by evanca9064619d agoSKILL.md
ai-job-searchby MadsLorentzen10044.9ktodayCLAUDE.md
claude-howtoby luongnv8910041.7k3d agoCLAUDE.md
algorithmic-artby anthropics100177.9k11d agoSKILL.md
pptxby anthropics100177.9k11d agoSKILL.md

Frequently asked questions

How do I install riverpod?
Run npx skills add evanca/flutter-ai-rules --skill riverpod. The install tabs above show the steps for each supported agent.
Which AI agents does riverpod work with?
It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
Is riverpod safe to use?
It is MIT-licensed and scores 100/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
Is riverpod still maintained?
The repository was last updated 19 days ago, so riverpod is actively maintained.

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: [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 ProviderContainer or ProviderScope for each test — never share state between tests.
  • Use container.listen over container.read for autoDispose providers to keep state alive during the test.
  • Use overrides to inject mocks or fakes.
  • Prefer mocking dependencies (repositories) rather than Notifiers directly.
  • If you must mock a Notifier, subclass the original — don't use implements or 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(...)).

References

Related Skills

View on GitHub
GitHub Stars646
CategoryDevelopment
Updated19d ago
Forks67

Languages

Shell

Trust signals

100/100

From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.

No cautions