はじめに
前回の記事(DI(依存性の注入)とは依存性を注入するということである、、?)では
DIとは何かについて記述しました。
今回はDagger2を導入してみたので記事にまとめます。
言語はKotlinです!
参考にしたチュートリアルはこちらです。
準備
まず、build.gradle
にてDagger2
を導入します。
参照:Dagger README.md
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. 提供するオブジェクトを把握する
まずは、どのオブジェクトが何に依存しているのかを開発者が把握する必要がある。
どのオブジェクトを注入するのか
自分が依存関係を把握できたら、次はアノテーションを用いて「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つがあります。
*数字は優先度
- Constructor Injection
- 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 を適用するのが良いと思います。
ただし、Activity
やFragment
などの Android環境独自の Lifecycleを持つオブジェクトに対しては使えません。
-> Field Injectionを使用する
2. Field Injection
e.g.
class PlayCallListFragment : Fragment() {
@Inject
lateinit var viewModel: PlayCallListViewModel
..
}
class PlayCallRepository {
@Inject
lateinit var dao: PlayCallDao
..
@Inject
アノテーションをつけるだけだと、Daggerは依存性を注入してくれません
-> @Component を作成し、inject用メソッドを用意する必要がある。
e.g.
@Component(modules = [AppModule::class])
interface AppComponent {
fun inject(repository: PlayCallListRepository)
}
ここで定義したinject(repository:)
を使用し対象オブジェクト(ここでは PlayCallListRepository
)に依存性を注入します。
このメソッドを呼んだ後に、@Inject
を使用することができます。
* 逆にいうと、inject(repository:)
より前に@Inject
を通ってしまうとコンパイルエラーが発生します。
e.g. PlayCallRepository
にて
class PlayCallRepository {
@Inject
lateinit var dao: PlayCallDao
init {
DaggerAppComponent.create().inject(this)
}
}
e.g. PlayCallListFragment
にて
class PlayCallListFragment : Fragment() {
@Inject
lateinit var viewModel: PlayCallListViewModel
override fun onAttach(context: Context) {
DaggerPlayCallListComponent.create().inject(this)
}
}
1つ目の例で少し解説します。
-
DaggerAppComponent.create()
でDaggerAppComponent
を生成します
-
AppComponent
内で定義したinject(repository:)
メソッドを呼ぶことで@Inject
アノテーションが付いているプロパティに依存性を注入することができます
補足:
DaggerAppComponent
はDaggerが自動生成したクラスです。
このクラス内で、AppComponent
で定義したインターフェースの処理を実装しています。
正確に言うと、DaggerAppComponent
内から別クラスのメソッドを呼ぶ処理を実装しています。
Daggerが自動生成したDaggerAppComponent
クラスを見てみましょう!
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;
}
..
}
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 では、他にも様々な機能・アノテーションが用意されています。
それらを使いこなすのは難しく感じていますが、少しずつ理解していこうと思います。
サンプルコード
参考資料
- Dagger: 公式ガイド
- Dagger: リポジトリ
- Dagger 2 Tutorial For Android: Advanced: チュートリアル