What does Riverpod's code generation (@riverpod annotation) add over manually-declared providers?
Short Answer
The @riverpod annotation (via riverpod_generator) generates the provider boilerplate for you from a plain function or class, reducing verbosity and catching more mistakes at compile time, at the cost of needing a code-generation build step.
Manually declaring final userProvider = FutureProvider<User>((ref) => fetchUser()); works fine, but as an app grows, choosing the right provider type for each case adds boilerplate. With @riverpod, you write a plain annotated function: @riverpod Future<User> user(UserRef ref) => fetchUser(); and the generator produces the correctly-typed provider automatically, inferring the right variant from your function's return type.
The practical gotcha: this requires running dart run build_runner watch --delete-conflicting-outputs during development so generated .g.dart files stay in sync — forgetting this leaves you editing a provider whose generated code is stale, causing confusing type errors unrelated to your actual change.
Code Example
// user_provider.dart
part 'user_provider.g.dart';
@riverpod
Future<User> user(UserRef ref) async {
return ref.watch(userRepositoryProvider).getUser();
}Common Mistakes
- ×Forgetting to run build_runner in watch mode during development, then being confused by type errors referencing outdated generated code.
- ×Committing the generated .g.dart files inconsistently across a team without a deliberate decision.
Interview Tips
- →Mentioning the build_runner watch-mode requirement specifically signals real hands-on usage.