0Pricing
Flutter Mobile Development · 강의

riverpod_generator 및 @riverpod를 활용한 코드 생성

riverpod_generator 주석을 사용해 상용구 코드 없이 타입 안전한 제공자를 생성합니다.

riverpod_generator 및 @riverpod를 활용한 코드 생성은(는) CoddyKit의 무료 Flutter Mobile Development 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Flutter Mobile Development 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Flutter Mobile Development 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

Why Code Generation?

Before Riverpod 2.0, you picked the right provider type by hand: Provider, StateProvider, FutureProvider, StreamProvider, NotifierProvider, and so on. Choosing wrong meant rewrites.

The riverpod_generator package flips this around. You write a plain function or class and add the @riverpod annotation. The generator inspects your return type and produces the correct, fully type-safe provider for you.

  • Less boilerplate — no manual provider declarations.
  • Type-safe parameters — pass arguments without .family gymnastics.
  • Auto-disposed by default — generated providers behave like autoDispose.

Adding the Dependencies

Code generation needs both runtime and dev-time packages. riverpod_annotation ships the @riverpod annotation you use in source. riverpod_generator and build_runner run the build step that emits the .g.dart files.

A typical pubspec.yaml for a Flutter app looks like this.

dependencies:
  flutter:
    sdk: flutter
  flutter_riverpod: ^2.5.1
  riverpod_annotation: ^2.3.5

dev_dependencies:
  build_runner: ^2.4.11
  riverpod_generator: ^2.4.0
  custom_lint: ^0.6.4
  riverpod_lint: ^2.3.10

Your First Generated Provider

The smallest generated provider is a top-level function annotated with @riverpod. The first parameter is always a Ref object; the return type decides everything.

Because this function returns a plain String synchronously, the generator emits a read-only provider exposing that value. You consume it via ref.watch(helloWorldProvider) exactly like a hand-written Provider<String>.

Note the two required pieces: the part directive and the // ignore_for_file comment is optional — but the part 'file.g.dart'; is mandatory.

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'hello.g.dart';

@riverpod
String helloWorld(Ref ref) {
  return 'Hello, Riverpod 2.0';
}

Running the Generator

The annotation alone does nothing until build_runner generates the companion .g.dart file. Run it from the project root.

  • One-off build: generates once and exits. --delete-conflicting-outputs clears stale generated files.
  • Watch mode: regenerates automatically every time you save a source file — ideal during active development.

After it finishes, the helloWorldProvider symbol becomes available for import.

# Generate once
dart run build_runner build --delete-conflicting-outputs

# Or watch and rebuild on save
dart run build_runner watch --delete-conflicting-outputs

Return Type Drives the Provider

The generator reads your return type and silently picks the matching provider kind. This is the core convenience of code generation: you never name a provider type again.

  • Return T → synchronous provider (like Provider<T>).
  • Return Future<T> → async provider exposing AsyncValue<T> (like FutureProvider).
  • Return Stream<T> → stream provider exposing AsyncValue<T> (like StreamProvider).

Below, simply changing the signature to Future turns it into an async provider — no other change needed.

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'user.g.dart';

@riverpod
Future<String> userName(Ref ref) async {
  await Future<void>.delayed(const Duration(seconds: 1));
  return 'Ada Lovelace';
}

Passing Parameters (No More .family)

With hand-written providers, parameterizing meant .family and a tuple-like single argument. The generator lets you add normal function parameters after ref, and they become strongly typed provider arguments.

Here messageProvider takes an int id. You call it as ref.watch(messageProvider(42)). Multiple parameters and named/optional parameters all work.

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'message.g.dart';

@riverpod
Future<String> message(Ref ref, int id) async {
  final repo = ref.watch(messageRepositoryProvider);
  return repo.fetchById(id);
}

// Usage in a widget:
// final msg = ref.watch(messageProvider(42));

Stateful Logic: The Notifier Class

For mutable state with methods, annotate a class that extends the generated base class _$ClassName. You override build() to return the initial state; the generator wires up a NotifierProvider for you.

Inside methods you mutate state, and listeners rebuild automatically. This replaces the old Notifier + manual NotifierProvider declaration with a single annotated class.

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'counter.g.dart';

@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;

  void increment() => state++;
  void reset() => state = 0;
}

Async Notifiers

If your build() returns a Future, the generator produces an AsyncNotifier. The exposed state is an AsyncValue<T> that automatically tracks loading, data, and error states.

To update state after an async action, assign AsyncValue.guard(...) to state — it runs your async code and captures success or error without manual try/catch.

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'todos.g.dart';

@riverpod
class Todos extends _$Todos {
  @override
  Future<List<String>> build() async {
    return ref.watch(todoRepositoryProvider).fetchAll();
  }

  Future<void> add(String title) async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() async {
      await ref.read(todoRepositoryProvider).create(title);
      return ref.read(todoRepositoryProvider).fetchAll();
    });
  }
}

Consuming Generated Providers

Generated providers are consumed exactly like manual ones — the generated symbol is <name>Provider for functions, or <ClassName>Provider for Notifier classes.

  • ref.watch(counterProvider) → the current state value.
  • ref.read(counterProvider.notifier) → the Notifier instance, to call methods like increment().
  • For async providers, watch returns an AsyncValue you handle with .when(...).
class CounterView extends ConsumerWidget {
  const CounterView({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Column(
      children: [
        Text('Count: $count'),
        ElevatedButton(
          onPressed: () => ref.read(counterProvider.notifier).increment(),
          child: const Text('Add'),
        ),
      ],
    );
  }
}

Keep-Alive and Dependencies

Generated providers are auto-disposed by default — they drop their state when no longer watched. Two annotation options give you control:

  • @Riverpod(keepAlive: true) — keeps the provider alive even with no listeners (use for app-wide singletons like a Dio client).
  • @Riverpod(dependencies: [...]) — declares scoped overrides for provider scoping. Most apps don't need this.

The capitalized @Riverpod(...) form is just the configurable version of the lowercase @riverpod shorthand.

import 'package:dio/dio.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'http.g.dart';

@Riverpod(keepAlive: true)
Dio dio(Ref ref) {
  return Dio(BaseOptions(baseUrl: 'https://api.example.com'));
}

Pure Dart: Why the Logic Is Testable

A big payoff of code generation is that your provider bodies are plain Dart functions and classes — easy to reason about and unit test. Below is a standalone illustration of the same state++ mutation logic a generated Notifier would run, with no Flutter or Riverpod imports needed.

This kind of pure logic is exactly what you keep inside a generated @riverpod class so it stays trivial to test.

class Counter {
  int state = 0;
  void increment() => state++;
  void reset() => state = 0;
}

void main() {
  final counter = Counter();
  counter.increment();
  counter.increment();
  counter.increment();
  print('After 3 increments: ${counter.state}');
  counter.reset();
  print('After reset: ${counter.state}');
}

Quick Check

You annotate a function that returns Future<List<Product>> with @riverpod. What kind of provider does riverpod_generator emit, and how do you consume it in a widget?

Recap

You learned how riverpod_generator removes provider boilerplate:

  • Add riverpod_annotation (runtime) plus riverpod_generator and build_runner (dev), and a part '<file>.g.dart'; directive.
  • Annotate a function for read-only/derived values, or a class extending _$Name for stateful Notifiers.
  • The return type chooses the provider: T → sync, Future<T> → async (AsyncValue), Stream<T> → stream.
  • Add normal parameters after ref instead of .family.
  • Run dart run build_runner watch to regenerate on save.
  • Providers are auto-disposed by default; use @Riverpod(keepAlive: true) for app-wide singletons.

자주 묻는 질문

“riverpod_generator 및 @riverpod를 활용한 코드 생성” 강의는 무료인가요?

네 — “riverpod_generator 및 @riverpod를 활용한 코드 생성” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Flutter Mobile Development 강의 전체를 잠금 해제할 수 있습니다. Flutter Mobile Development 강의에는 총 4개의 강의가 포함되어 있습니다.

“riverpod_generator 및 @riverpod를 활용한 코드 생성”에서 뭘 배우나요?

riverpod_generator 주석을 사용해 상용구 코드 없이 타입 안전한 제공자를 생성합니다. 브라우저에서 직접 실행하는 실습 코드로 Flutter Mobile Development을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Flutter Mobile Development을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Flutter Mobile Development은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“riverpod_generator 및 @riverpod를 활용한 코드 생성” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Flutter Mobile Development 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Flutter Mobile Development 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Provider에서 Riverpod로: 레거시 상태 마이그레이션
  2. riverpod_generator 및 @riverpod를 활용한 코드 생성
  3. AsyncNotifier 및 FutureProvider 데이터 파이프라인
  4. Provider 범위 지정, 재정의 및 ProviderObserver
← Flutter Mobile Development(으)로 돌아가기