implementing-riverpod — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited implementing-riverpod (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Implement compile-time safe, context-free state management using Riverpod. Use @riverpod code generation for all new code — the generator handles auto-dispose, family, and type safety automatically.
Code Generation (Recommended):
dependencies:
flutter_riverpod: ^3.3.1
riverpod_annotation: ^4.0.2
dev_dependencies:
build_runner: ^2.14.1
riverpod_generator: ^4.0.3
riverpod_lint: ^3.1.3Manual only (no code generation):
dependencies:
flutter_riverpod: ^3.3.1void main() {
runApp(ProviderScope(child: MyApp()));
}Notifier (synchronous mutable state):
part 'counter.g.dart';
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
void decrement() => state--;
}
// Generated: counterProviderAsyncNotifier (async mutable state):
@riverpod
class TodoList extends _$TodoList {
@override
Future<List<Todo>> build() async => fetchTodos();
Future<void> addTodo(String title) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
final todo = await apiClient.createTodo(title);
return [...await future, todo];
});
}
}
// Generated: todoListProviderFutureProvider with auto-retry:
@Riverpod(retry: myRetryStrategy)
Future<User> userProfile(Ref ref, String userId) async {
return fetchUser(userId);
}
Duration? myRetryStrategy(int retryCount, Object error) {
if (retryCount >= 5) return null;
return Duration(milliseconds: 200 * (1 << retryCount));
}→ See riverpod.md for manual providers, testing, Mutations, and offline persistence.
Prefer `Consumer` to scope rebuilds to the smallest subtree. ConsumerWidget rebuilds the entire widget on every state change; Consumer rebuilds only its builder.
// Preferred: only Text rebuilds when count changes
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer(
builder: (context, ref, _) {
final count = ref.watch(counterProvider);
return Text('Count: $count');
},
),
floatingActionButton: FloatingActionButton(
// ref.read in callbacks — never ref.watch
onPressed: () => ProviderScope.containerOf(context)
.read(counterProvider.notifier)
.increment(),
child: const Icon(Icons.add),
),
);
}
}Use ConsumerWidget only when the entire widget tree depends on provider state:
class CounterPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
appBar: AppBar(title: Text('Count: $count')),
body: CounterBody(count: count),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Icon(Icons.add),
),
);
}
}Use ref.watch(provider.select((s) => s.field)) to rebuild only when a specific field changes:
Consumer(
builder: (context, ref, _) {
// Only rebuilds when isLoggedIn changes, ignoring other User fields
final isLoggedIn = ref.watch(authProvider.select((u) => u.isLoggedIn));
return isLoggedIn ? const HomeView() : const LoginView();
},
)Use HookConsumerWidget when combining with flutter_hooks. Use ref.listen for side-effects (snackbars, navigation) without triggering rebuilds.
→ See riverpod.md for detailed Consumer vs ConsumerWidget decision table and select() patterns.
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProfileProvider('u1'));
return userAsync.when(
data: (user) => Text('Hello, ${user.name}'),
loading: () => const CircularProgressIndicator(),
error: (e, st) => Text('Error: $e'),
);
}→ See PITFALLS.md for AsyncValue best practices and optimistic UI with copyWithPrevious.
@riverpod
Future<List<Post>> userPosts(Ref ref, String userId) async {
final user = await ref.watch(userProfileProvider(userId).future);
return fetchPostsForUser(user.id);
}→ See PITFALLS.md for ref.mounted checks, memory leak prevention, and ref.read vs ref.watch rules.
ref.mounted, memory leaks, ref.read vs ref.watch, AsyncValue patternsref only.@riverpod code generation; catches errors at build time.== for all state comparisons — implement operator == or use Freezed.keepAlive: true.ref in a variable or pass it outside provider scope.AsyncValue.guard().ref.mounted after await before updating state.ref.onDispose.ref.read in event handlers; ref.watch only in build().Provider with ref.onDispose.Consumer over ConsumerWidget; use select() for field-level precision.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.