🐥

FlutterにおけるDependency Injection入門

2024/05/18に公開

Flutterを学び始めたばかりの方にとって、「Dependency Injection(依存性注入)」という言葉は少し難しく感じるかもしれません。

しかし、DI(Dependency Injection)はアプリケーションの設計をシンプルにし、テストしやすくするために重要な概念です。

この記事では、FlutterでのDIについてわかりやすく説明します。

Dependency Injectionとは?

まず、Dependency Injectionが何かを理解しましょう。簡単に言うと、DIはオブジェクトが依存する他のオブジェクトを外部から提供する仕組みです。これにより、オブジェクト同士の依存関係を管理しやすくなり、コードの再利用性やテストのしやすさが向上します。

なぜDependency Injectionが必要なのか?

DIを使うことで、以下のようなメリットがあります:

  1. コードの再利用性向上:
    依存関係を外部から注入することで、同じオブジェクトを異なるコンテキストで再利用しやすくなります。

  2. テストのしやすさ:
    依存関係をモックやスタブに置き換えることで、ユニットテストやインテグレーションテストが容易になります。

  3. メンテナンス性向上:
    依存関係が明確になるため、コードの理解や変更がしやすくなります。

FlutterでのDependency Injectionの方法

FlutterでDIを実現するための方法はいくつかありますが、ここでは代表的な方法をいくつか紹介します。

1. コンストラクタインジェクション

コンストラクタインジェクションは、最も基本的なDIの方法です。オブジェクトのコンストラクタに依存関係を渡すことで実現します。

DIができていない例

Dependency Injection (DI) ができていない例では、クラスが自分で依存するオブジェクトを直接作成したり、依存関係を明示的に渡さずに使用したりします。以下に、DIができていない例を示します:

class ApiService {
  void fetchData() {
    // データを取得する処理
  }
}

class MyRepository {
  final ApiService apiService = ApiService();  // 依存しているApiServiceを直接生成

  void loadData() {
    apiService.fetchData();
  }
}

void main() {
  MyRepository repository = MyRepository();
  repository.loadData();
}

問題点の解説

  1. 依存関係の直接インスタンス化
    ApiService apiService = ApiService();
    MyRepositoryクラスの中で、ApiServiceのインスタンスが直接作成されています。これにより、MyRepositoryはApiServiceに強く結びついてしまい、テストやモックの挿入が難しくなります。

  2. 柔軟性の欠如
    クラスが自分で依存するオブジェクトを作成すると、異なる実装やモックを注入することができなくなります。

  3. 再利用性の低下
    依存関係がクラス内で固定されるため、再利用が難しくなります。

改善(コンストラクタインジェクションによるDIの)例

以下は、依存関係注入を使用した改善例です:

class ApiService {
  void fetchData() {
    // データを取得する処理
  }
}

class MyRepository {
  final ApiService apiService;

  MyRepository(this.apiService);

  void loadData() {
    apiService.fetchData();
  }
}

void main() {
  ApiService apiService = ApiService(); // ApiServiceのインスタンスを作成
  MyRepository repository = MyRepository(apiService); // ApiServiceのインスタンスをMyRepositoryのコンストラクタに渡す

  repository.loadData();
}

この改善例では、ApiServiceのインスタンスが外部から注入され、MyRepositoryはその依存関係に依存することなく動作します。これにより、テストやモックの挿入が容易になり、コードの柔軟性とメンテナンス性が向上します。

2. Riverpodパッケージ

Flutterでは、Riverpodパッケージを使ってDIを簡単に実現できます。
https://riverpod.dev/ja/

DIができていない例

まず、Riverpodを使わずに依存関係注入ができていない例を示します。

import 'package:flutter/material.dart';

class ApiService {
  void fetchData() {
    // データを取得する処理
  }
}

class MyRepository {
  final ApiService apiService = ApiService(); // 依存関係を直接インスタンス化

  void loadData() {
    apiService.fetchData();
  }
}

class MyHomePage extends StatelessWidget {
  final MyRepository repository = MyRepository(); // 依存関係を直接インスタンス化

  
  Widget build(BuildContext context) {
    repository.loadData();
    return Scaffold(
      appBar: AppBar(
        title: Text("Home Page"),
      ),
      body: Center(
        child: Text("Home Page Content"),
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    home: MyHomePage(),
  ));
}

この例では、MyRepositoryとMyHomePageの中で依存関係が直接インスタンス化されています。これにより、テストやモックの挿入が困難になり、柔軟性が低下します。

Riverpodにより改善した例

次に、Riverpodを用いて依存関係注入を改善した例を示します。

Step1. RiverpodのProviderを定義

まず、プロバイダを定義します。これにより、依存関係を簡単に管理できます。

import 'package:flutter_riverpod/flutter_riverpod.dart';

final apiServiceProvider = Provider((ref) => ApiService());
final myRepositoryProvider = Provider((ref) => MyRepository(ref.read(apiServiceProvider)));

class ApiService {
  void fetchData() {
    // データを取得する処理
  }
}

class MyRepository {
  final ApiService apiService;

  MyRepository(this.apiService);

  void loadData() {
    apiService.fetchData();
  }
}

Step2. プロバイダを使用して依存関係を注入

次に、Riverpodのプロバイダを使って依存関係を注入します。これにより、依存関係の管理が容易になり、テストやモックの挿入が可能になります。

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends ConsumerWidget {
  
  Widget build(BuildContext context, ScopedReader watch) {
    final repository = watch(myRepositoryProvider);

    repository.loadData();

    return Scaffold(
      appBar: AppBar(
        title: Text("Home Page"),
      ),
      body: Center(
        child: Text("Home Page Content"),
      ),
    );
  }
}

改善した例では、ApiServiceとMyRepositoryをプロバイダとして定義し、依存関係を管理しています。ProviderScopeを使ってプロバイダを提供し、ConsumerWidgetを使って依存関係を注入しています。これにより、依存関係の管理が容易になり、テストやモックの挿入が可能になります。

まとめ

Dependency Injectionは、Flutterアプリケーションの設計において、非常に重要な概念です。DIを正しく実装することで、コードの再利用性やテストのしやすさが向上し、メンテナンス性も高まります。

コンストラクタインジェクションやRiverpodなどのDI手法を活用して、より良いFlutterアプリケーションを作成しましょう。

Discussion