Bidev
IntermediateState Management

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.