👋

【Android】はじめてのDagger2

2021/03/28に公開

はじめに

前回の記事(DI(依存性の注入)とは依存性を注入するということである、、?)では
DIとは何かについて記述しました。

今回はDagger2を導入してみたので記事にまとめます。
言語はKotlinです!

参考にしたチュートリアルはこちらです。

準備

まず、build.gradleにてDagger2を導入します。
参照:Dagger README.md

build.gradle(app)

apply plugin: 'kotlin-kapt' // Kotlinでアノテーションを使用するために必要

dependencies {
    ..

    // 2020/4/26現在、
    // バージョンによって、使用できるアノテーションの種類が異なる.
    // また、2.25 ではコードが自動生成されない問題があることを確認済み.
    def dagger_version = 2.27 

    implementation "com.google.dagger:dagger:$dagger_version"
    annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"

    // Kotlinで開発する場合は必要.
    // KotlinでもDaggerに関するアノテーションを使用できるようにするためのもの.
    kapt "com.google.dagger:dagger-compiler:$dagger_version" 
}

Daggerの概要

Daggerとは、オブジェクト間の依存関係を管理するコードを自動生成してくれるツールです。

Daggerがコードを自動生成するために必要なこと:

  1. 提供するオブジェクトを把握する
  2. 注入先のオブジェクトを把握する
  3. 注入を実行する

1. 提供するオブジェクトを把握する

まずは、どのオブジェクトが何に依存しているのかを開発者が把握する必要がある。

どのオブジェクトを注入するのか

自分が依存関係を把握できたら、次はアノテーションを用いて「Dagger」にそれを教えてあげる

e.g.

@Module
class DatabaseModule {
    @Provides
    fun providePlayCallDao(): PlayCallDao {
        return App.database.playCallDao()
    }
}

@Provides

@Provides(提供)したいオブジェクトのインスタンス化メソッドにつけることで、何を@ProvidesしたいかをDaggerに知らせる。

@Module

@Providesするためのメソッドを、@Moduleクラスにまとめる。

他の@Moduleも含めることができる。

e.g.

@Module
class DatabaseModule

@Module(includes = [DatabaseModule::class])
class RepositoryModule

ちょこっとまとめ

提供するオブジェクトをDaggerに知らせることができた!

-> これで、Daggerはコードを自動生成することができる。そして、@Providesするオブジェクトの生成方法を覚えてくれる。

2. 注入先のオブジェクトを把握する

Daggerは、1.の過程で何を注入するのかを把握することができた。
しかし、@Providesされるオブジェクトをどこで@Injectするのかは把握できていない。

-> どこで使うためのものなのかを知らせてあげよう!

注入したいタイミングで@Injectすれば良いが、後述の Field Injection を用いる場合は@Componentを作成する必要があります。
ここでは、その@Componentについて説明します。

@Component

@Componentを定義し、以下の役割を担わせる。
これは後述の Field Injection で使用します。

役割:

  • @Providesするオブジェクトを解放する
  • 目標のクラスに依存性を注入する

含まれるもの:

  • 使用する@Moduleの宣言
  • inject用メソッドのインターフェース

    このメソッドの引数に injectする対象のクラス(@Injectを記述するクラス)の型を設定します。

    ここで定義しておけば、あとは Dagger が処理内容を自動生成してくれます。

    inject()の使用方法は次節に持ち越します。

e.g.

@Component(modules = [DatabaseModule::class])
interface AppComponent {
    // Daoの注入
    fun inject(repository: PlayCallListRepository)
}

3. 注入を実行する

@Inject を用いて実装します。

@Inject

@Injectしたいオブジェクトを宣言している箇所につけます。
例は後述します。

Daggerが提供するDIの種類

Daggerが提供するDIの種類は基本的に以下の2つがあります。
*数字は優先度

  1. Constructor Injection
  2. Field Injection

優先度の理由と各種詳細を以下に記述します。

1. Constructor Injection

e.g.

class PlayCallRepository @Inject constructor(
    private val dao: PlayCallDao
) {
    // このメソッドの実装は Dagger には関係ありません
    suspend fun loadAllPlayCall(): List<PlayCallEntity> {
        return dao.loadAllPlayCall()
    }
}

1番良さげな理由:

  • オブジェクト生成時に依存関係をまとめて設定できるから
    (ここでは注入するものがPlayCalldaoのみですが)
  • 実装が手軽だから
    @Componentを作成しなくても良い

なので、可能であれば Contructor Injection を適用するのが良いと思います。

ただし、ActivityFragmentなどの Android環境独自の Lifecycleを持つオブジェクトに対しては使えません。
-> Field Injectionを使用する

2. Field Injection

e.g.

PlayCallListFragment.kt
class PlayCallListFragment : Fragment() {
    @Inject
    lateinit var viewModel: PlayCallListViewModel
    ..
}
PlayCallRepository.kt
class PlayCallRepository {
    @Inject
    lateinit var dao: PlayCallDao
    ..

@Injectアノテーションをつけるだけだと、Daggerは依存性を注入してくれません
-> @Component を作成し、inject用メソッドを用意する必要がある。

e.g.

AppComponent.kt
@Component(modules = [AppModule::class])
interface AppComponent {
  fun inject(repository: PlayCallListRepository)
}

ここで定義したinject(repository:)を使用し対象オブジェクト(ここでは PlayCallListRepository)に依存性を注入します。

このメソッドを呼んだ後に、@Injectを使用することができます。
* 逆にいうと、inject(repository:)より前に@Injectを通ってしまうとコンパイルエラーが発生します。

e.g. PlayCallRepository にて

PlayCallRepository.kt
class PlayCallRepository {
    @Inject
    lateinit var dao: PlayCallDao
    init {
        DaggerAppComponent.create().inject(this)
    }
}

e.g. PlayCallListFragment にて

PlayCallListFragment.kt
class PlayCallListFragment : Fragment() {
    @Inject
    lateinit var viewModel: PlayCallListViewModel

    override fun onAttach(context: Context) {
        DaggerPlayCallListComponent.create().inject(this)
    }
}

1つ目の例で少し解説します。

  1. DaggerAppComponent.create()DaggerAppComponentを生成します
  2. AppComponent内で定義したinject(repository:)メソッドを呼ぶことで@Injectアノテーションが付いているプロパティに依存性を注入することができます

補足:
DaggerAppComponentはDaggerが自動生成したクラスです。
このクラス内で、AppComponentで定義したインターフェースの処理を実装しています。
正確に言うと、DaggerAppComponent内から別クラスのメソッドを呼ぶ処理を実装しています。
Daggerが自動生成したDaggerAppComponentクラスを見てみましょう!

DaggerAppComponent.java
public final class DaggerAppComponent implements AppComponent {
  ..
  @Override
  public void inject(PlayCallRepository repository) {
    // ここで、inject(repository:) を実装している
    injectPlayCallRepository(repository);  // メソッドA
  }

  // メソッドA
  private PlayCallRepository injectPlayCallRepository(PlayCallRepository instance) {
  // 次に添付のクラス内の injectDao() を呼ぶ
  PlayCallRepository_MembersInjector.injectDao(instance, DatabaseModule_ProvidePlayCallDaoFactory.providePlayCallDao(databaseModule));
    return instance;
  }
  ..
}
PlayCallRepository_MembersInjector.java
public final class PlayCallRepository_MembersInjector implements MembersInjector<PlayCallRepository> {
  ..
  @InjectedFieldSignature("io.github.itakahiro.architecturefootball.repository.PlayCallRepository.dao")
  public static void injectDao(PlayCallRepository instance, PlayCallDao dao) {
    // ここで、PlayCallRepository に PlayCallDao を注入している!!
    instance.dao = dao;
  }
}

まとめ

Dagger の基礎的な部分をまとめてみました。
Dagger では、他にも様々な機能・アノテーションが用意されています。
それらを使いこなすのは難しく感じていますが、少しずつ理解していこうと思います。

サンプルコード

https://github.com/iTakahiro/ArchitectureFootball

参考資料

Discussion