[Dart/Flutter]DI (Dependency Injection/依存性の注入)をGetItで実現する

FlutterでDI (Dependency Injection ・依存性の注入)を実施する方法を解説します。そのためのプラグイン「get_it」をご紹介します。

なぜ必要か

基本的にプログラムをしていると、そのままコンストラクタを使用して、クラスをインスタンス化します。

var instance = InstanceClass();

しかし、こうするとソース内にどのクラスを呼ぶか、というのが定義されてしまい、変更することができません。何を言っているんだ、と思われると思います。
たとえば、ちょっとした個人開発でもFireStoreを使います。その場合、開発中の環境と、リリース時の環境を区別したい、というケースがほとんどです。そのときは実行環境でFirebaseの定義ファイルを切り替えます。それを、クラスでもやろう、という話です。DIを使えば、開発環境とリリース時で使用するクラスを変更できますし、また単体テストと総合テストで使用するクラスを変えることもできます。
具体的には、クラスAの中で呼び出すクラスBがある。クラスAをテストコードでテストしているときはクラスBをダミーデータにしたい。しかし実際にアプリで実施するときは、ちゃんとした実装を呼びたい、というケースです。後で例を見ながら説明します。

インストール方法

flutter pub add get_it

ソース

どのクラスを呼び出すか、定義する

import 'package:get_it/get_it.dart';
 
GetIt.I.registerSingleton<firebasefirestore>(FirebaseFirestore.instance);
GetIt.I.registerLazySingleton<firebasefirestore>(()=>FirebaseFirestore.instance);

GetIt.instanceと書くのが正式ですが、その省略形のGetIt.Iと書きます。
この場合、FirebaseFirestoreというクラスが使いたいの例です。使用したいクラス名に変更してください。
定義した時点でインスタンスを生成したい場合はregisterSingleton、実際に参照されたときにインスタンスを生成したい場合はregisterLazySingletonを使用します。
引数として、実際のインスタンスを書きます。この場合、FirebaseFirestore.instanceでインスタンスを渡しています。普通にコンストラクタを書いたり、変数を定義して渡すことも可能です。

定義したクラスを呼び出す

final firestore = GetIt.I.get<FirebaseFirestore>();
final firestore = GetIt.I<FirebaseFirestore>();

GetIt.I.get<クラス名>()で定義したクラスに対応するインスタンスを取得します。またgetを省略してGetIt.I<クラス名>で登録したインスタンスが参照できます。

実際にどのように使用するか

アプリ実行時

mainの中の実行直後に定義する。こちらには、アプリで実行する実際のデータを入れる。カウントアップアプリのint countをCountDataという一つのクラスにまとめたアプリと考えてください。

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  GetIt.I.registerSingleton<Future<SharedPreferences>>(
      SharedPreferences.getInstance());
  GetIt.I.registerSingleton<Future<CountData>>(Future.value(CountData(count:0)));

  runApp(const MyApp());
}

テスト

テストの最初に初期化します。同じテスト内で値を変更できるように、setUpAllのなかで、GetIt.I.allowReassignment = true と設定します。

void main() {
  setUpAll(() async {
    GetIt.I.allowReassignment = true;
  });

  testWidgets('get_it', (WidgetTester tester) async {
    CountData dummy = CountData(count: 123456789);

    GetIt.I.registerSingleton<Future<CountData>>(
      Future.value(dummy),
    );
    GetIt.I.registerSingleton<Future<SharedPreferences>>(
      SharedPreferences.getInstance(),
    );
    await tester.pumpWidget(const MyApp());
    await tester.pump();

    expect(find.text('123,456,789'), findsOneWidget);
  });
}

値に1億が入っているときの表示を(この場合3桁区切り)確認したい場合、1億回タップして確認するという手もあります。しかし、上記のように「何の値を入れるか」を定義して代入するやり方の方が応用が利きます。

まとめ

ということで、DIを使用するためGetItの解説をしました。環境によりクラスを入れ替えたいケース、実行環境によりDBを変えるとか、テスト時にモックを使うとか、そんなケースに使用できます。

参考

  • 本家
  • Flutter Meetup Osaka #8
    1:30:00から (私の登壇です)。各DI用のプラグインの解説も行ってます。
  • サンプルソース
    get_itとshared_preferencesの説明も混ざったソースなので、ごちゃごちゃしてます