0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Riverpod を使った Dependency Injection(DI)超入門

Posted at

Riverpod を使った Dependency Injection(DI)超入門

Flutter で 「依存関係を外から差し込む」= Dependency Injection を行うと、

  • 再利用性 が上がる
  • テストしやすく なる
  • 実装と実装をつなぐ 結合度 が ぐっと下がる

Riverpod は Provider という箱を通じて 「依存オブジェクトを生成し、必要な場所に安全に渡す」 仕組みを提供します。
ここでは 公式 Riverpod v2 系 を前提に、最小構成 → 実践パターン → テストでの差し替え、まで見てみましょう。


1. 最小サンプル ― ただのクラスを注入する

// 1) 依存クラス(例: API クライアント)
class WeatherApi {
  Future<String> fetch() async => '☀️ 25°C';
}

// 2) DI コンテナに当たる provider
final weatherApiProvider = Provider<WeatherApi>((ref) {
  return WeatherApi();
});

// 3) 利用側(ConsumerWidget や Consumer)で読み取る
class WeatherText extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final api = ref.read(weatherApiProvider);   // 依存を取得
    return FutureBuilder(
      future: api.fetch(),
      builder: (_, snap) => Text(snap.data ?? '...'),
    );
  }
}

ポイント 🔑

なに? 役割
WeatherApi 依存オブジェクト(サービス)
Provider インスタンスを 1 か所 で生成し、必要な所に渡す橋渡し
ref.read() 依存を取得(listen しない)

2. “依存が依存を持つ” とき ― 階層的 DI

// 設定値を読むリポジトリ
class ConfigRepository {
  String get baseUrl => 'https://api.example.com';
}

final configRepoProvider = Provider((_) => ConfigRepository());

// 依存を持つ API
class TodoApi {
  TodoApi(this.config);
  final ConfigRepository config;

  Future<List<String>> fetchTodos() async {
    // config.baseUrl を使って呼び出す想定
    return ['Take out trash', 'Buy milk'];
  }
}

// TodoApi も provider 化。中で依存を要求する
final todoApiProvider = Provider<TodoApi>((ref) {
  final config = ref.read(configRepoProvider);  // 依存解決
  return TodoApi(config);
});

メリット

  • どこでも ref.read(todoApiProvider) で取得可能
  • どの画面から呼んでも 同じインスタンス が使われる(=シングルトン管理)

3. State 管理と合わせる – Notifier / AsyncNotifier

API → リポジトリ → ViewModel と階層を刻む場合:

// ↓ API 依存を注入した AsyncNotifier
class TodoListNotifier extends AsyncNotifier<List<String>> {
  @override
  Future<List<String>> build() async {
    final api = ref.read(todoApiProvider);   // DI
    return api.fetchTodos();
  }

  Future<void> refresh() async { state = await AsyncValue.guard(build); }
}

final todoListProvider =
    AsyncNotifierProvider<TodoListNotifier, List<String>>(TodoListNotifier.new);

使用例:

class TodoScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final todos = ref.watch(todoListProvider);
    return todos.when(
      data: (list) => ListView(children: list.map(Text.new).toList()),
      loading: () => const CircularProgressIndicator(),
      error: (e, s) => Text('Error: $e'),
    );
  }
}

4. テストでの差し替え ― ProviderScope の override

本番環境では 実 API、テストでは スタブ を注入したいとき:

// テスト用ダミー
class FakeWeatherApi implements WeatherApi {
  @override
  Future<String> fetch() async => '🌧️ 12°C (fake)';
}

void main() {
  testWidgets('shows fake weather', (tester) async {
    await tester.pumpWidget(
      ProviderScope(
        overrides: [
          weatherApiProvider.overrideWithValue(FakeWeatherApi()),  // ← 差し替え
        ],
        child: const WeatherText(),
      ),
    );

    await tester.pump();                // FutureBuilder の完了待ち
    expect(find.text('🌧️ 12°C (fake)'), findsOneWidget);
  });
}

ポイント

  • overrideWithValue / overrideWithProvider好きな実装 に置き換え可能
  • テストだけでなく、開発フレーバー(staging / production)を切り替えるのも同じ手法で OK

5. よくある疑問 Q&A

Q A
ref.readref.watch の違いは? read は「1 回だけ取得」なので ビルドをトリガーしないwatch は値の変化を監視して Widget を再ビルド。依存注入目的なら基本 read で OK。
シングルトンにしたいときは? Provider はデフォルトでアプリ生存期間中 1 インスタンス。autoDispose を付けると未使用時に破棄。
DI コンテナは Riverpod だけで十分? ほとんどのケースで可。複雑な遅延ロードやスコープ切替が欲しければ Code generation (riverpod_generator) を併用。

まとめ

  1. 依存オブジェクトを Provider 化 – 生成責任を 1 箇所に閉じ込める
  2. 必要な場所で ref.read() – 明示的に取得し、テストでは override
  3. 構造が深くなっても階層的に DI – Provider はネストが得意
  4. テスト/フレーバー切替も同じ override で統一

Riverpod を DI コンテナとして “当たり前” に使うことで、
Flutter アプリのテスト性と保守性は 劇的 に向上します。
ぜひ 「まず Provider を切る」 から始めてみてください! 🎉

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?