riverpod-best-practices
Installation
SKILL.md
Riverpod Best Practices (v3)
This skill outlines the standard operating procedures for using Riverpod v3 in this project. Adherence to these practices ensures maintainability, testability, and consistency.
Core Principles
- Code Generation (
@riverpod): ALWAYS use the@riverpodannotation andriverpod_generator. Do NOT define providers manually (e.g.,Provider((ref) => ...)).- Why: Reduces boilerplate, handles
autoDisposelogic automatically, ensures type safety, and enables easier refactoring.
- Why: Reduces boilerplate, handles
- Modular Architecture: Organize code by feature, not by layer.
- Structure:
lib/modules/[feature]/providers/for providers,lib/modules/[feature]/screens/for UI.
- Structure:
- Unified
Ref: UseReffor all provider interactions. Avoid legacy types likeWidgetRefinside providers (useRefinstead). - AsyncValue First: Always handle Loading/Error/Data states explicitly in the UI using
switchexpressions.
Implementation Guidelines
1. Defining Providers
- Class-Based (Notifier): Use for complex state requiring methods (mutations).
class MyNotifier extends _$MyNotifier { ... } - Functional (Future/Stream): Use for simple data fetching or read-only values.
Future<Data> myData(Ref ref) async { ... } - KeepAlive: Use
@Riverpod(keepAlive: true)ONLY for global state (User, Auth, Settings). Default isautoDispose.
2. State Management
- Side Effects: Perform side effects (API calls, navigation logic) in methods within the Notifier, NOT in the
build()method. - Ref.mounted: Always check
ref.mountedafter anawaitbefore setting state to prevent exceptions on disposed providers.if (ref.mounted) state = AsyncData(data); - Mutations: To update lists/data, prefer fetching fresh data or optimistically updating state.
3. Consuming Providers
- ConsumerWidget: Extend
ConsumerWidgetinstead ofStatelessWidget. - ConsumerStatefulWidget: Extend
ConsumerStatefulWidgetif you needinitState/dispose. - Ref.watch: Use
ref.watchinsidebuild()to rebuild on changes. - Ref.read: Use
ref.readinside callbacks (e.g.,onPressed) to trigger actions. NEVER useref.readinsidebuild().
4. Naming Conventions
- File Name:
snake_case(e.g.,user_controller.dart). - Class Name:
PascalCase(e.g.,UserController). - Provider Name (Generated):
camelCase(e.g.,userControllerProvider).
Resources
- Code Snippets: See references/snippets.md for templates of Notifiers, Providers, and Consumers.
- Testing Guide: See references/testing.md for unit and widget testing patterns.
- Async Gaps & Lifecycle: See references/async_gaps.md for critical info on
UnmountedRefExceptionandkeepAlive. - Provider Types: See references/provider_types.md for a decision tree on which provider to use.
- Performance: See references/performance.md for
ref.selectand optimization tips. - Architecture: See references/architecture.md for repository patterns and anti-patterns.
Migration (from v2/Manual)
If you encounter manual providers (StateNotifierProvider, ChangeNotifierProvider, etc.):
- Identify: Locate the logic.
- Convert: Rewrite as a
@riverpodclass or function. - Replace: Update consumers to use the generated provider (e.g.,
myProviderinstead ofmyProvider.notifierfor watching state).
Related skills