【Kotlin/Android Studio】Dagger2の導入方法と使い方!DIとは?
この記事からわかること
- Android Studio/KotlinでDagger2の使い方
- DI(依存性注入)とは?
- 導入方法と仕組み
- アノテーションの種類
- @Injectや@Module、@Provides、@Componentの使い方と役割
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
参考文献:公式リファレンス:Daggerの基本
参考文献:Dagger
環境
- Android Studio:Flamingo
- Kotlin:1.8.20
- Dagger2:2.48
Dagger2とは?
Dagger2とはAndroidアプリ開発においてオブジェクト同士の依存関係を解決し、プロジェクト内のオブジェクトに対して依存性注入(DI)デザインパターンを活用するためのフレームワークです。JavaだけでなくKotlinにも対応しており、プロジェクトで利用されることの多いフレームワークの1つです。squareという会社が作成し、現在はGoogleが管理しているようです。
Dagger2ではアノテーションを付与して依存関係をDagger側に知らせることでコンパイル時に依存性を注入するために必要なコードを自動生成します。内部的にはオブジェクトグラフと呼ばれる依存性の提供元と要求先を保持したグラフを構築し依存性の注入を実現してるようです。この依存性を提供する方法を@Provides
、依存性を要求する場所を@Inject
を使用して定義していきます。
依存関係とは?
そもそも依存関係とはオブジェクト同士が依存している関係のことです。ここでいうオブジェクトはクラスや構造体、メソッドなども当てはまります。例えば以下はUserクラスがActionGameクラスに依存している関係になります。
class User {
var stamina: Int = 20
fun playGame() {
val actionGame = ActionGame()
if (actionGame.requiredStamina <= stamina) {
println("Playing")
stamina -= actionGame.requiredStamina
} else {
println("Out of Stamina...")
}
}
}
class ActionGame {
var requiredStamina: Int = 10
}
fun main() {
val user = User()
user.playGame()
println(user.stamina)
}
オブジェクト同士の依存関係やDIについての詳細は以下の記事を参考にしてください。
Dagger2で使用するアノテーション
Daggerではアノテーションを利用してオブジェクト間の依存関係を定義することで、コンパイル時にDaggerがアノテーションを識別し、適切な依存関係を注入してくれます。利用できるアノテーションの種類はたくさんあるので主要なものを役割を整理しておきます。
@Inject
役割:依存性を注入する対象
@Injectアノテーションは、依存性を注入する対象のコンストラクタ、フィールド、メソッドなどに適用されます。Daggerは@Injectアノテーションが付いた場所に依存性を注入します。
@Module
役割:依存性の提供元モジュール
@Moduleアノテーションは、Daggerに依存性の提供方法を定義するクラスに適用されます。@Moduleアノテーションを付けたクラス内で、@Providesアノテーションを使用して依存性を提供するメソッドを定義します。
@Provides
役割:依存性を提供するためのメソッド
@Providesアノテーションは、@Moduleアノテーションが付いたクラス内で使用され、依存性を提供するためのメソッドを宣言します。具体的には依存
@Component
役割:依存の提供元と要求先を紐付け
@Componentアノテーションは、Daggerに依存性のグラフを生成するためのインターフェースに適用されます。@Componentアノテーションを付けたインターフェースは、依存性の提供元と依存性の要求元を結びつけます。コンポーネントは依存性の注入を実行するエントリーポイントとして機能します。
@Scope
@アノテーションは、特定のスコープ(シングルトンなど)を定義するために使用されます。これにより、Daggerは同じスコープ内で一意のインスタンスを保持し、適切に再利用します。
導入方法
Dagger2を使用するためにはAndroid Studioの中にDagger2ライブラリを導入する必要があります。まずは「bundle.gradle(Module)」に以下のコードを記述します。追加する箇所が多いので注意してください。
plugins {
// 〜〜〜〜〜〜〜
id 'kotlin-kapt'
}
android {
// 〜〜〜〜〜〜〜
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
dependencies {
// 〜〜〜〜〜〜〜
def dagger_version = 2.48
implementation "com.google.dagger:dagger:$dagger_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
}
追加した箇所と項目
- plugins内:「id 'kotlin-kapt'」を追加
- android内:「 kotlinOptions { }」の周りを「tasks.with... { }」で囲む
- dependencies内:「def dagger_version = 2.48」以降全て
kotlin-kapt
は「kotlin-annotation-processing tools」の略で、JavaのPluggable Annotation Processing APIをKotlinで使えるようにするためのプラグインです。
記述できたら「Sync Now」をクリックします。私は「tasks.with... { }」がなかった(公式には載っていなかった)ため以下のようなエラーが発生しました。
これで導入は完了です。
Dagger2を使用した実装例
Androidで依存関係インジェクションを行う主な方法は2つあります。
- コンストラクタインジェクション
- フィールドインジェクション(セッターインジェクション)
コンストラクタインジェクション
コンストラクタインジェクションはその名前の通りコンストラクタを使用して依存性を挿入する方法です。シンプルな独自のクラスなどでは簡単にコンストラクタインジェクションを実装することができます。
フィールドインジェクション(セッターインジェクション)
ActivityやFragmentなどシステムによってインスタンス化されるクラスはコンストラクタインジェクションが不可能です。そのためクラスの作成後に依存関係がインスタンス化されます。
公式リファレンス:Android での依存関係インジェクション
コンストラクタインジェクション
実際にDagger2を使用してコンストラクタインジェクションを実装した小さなサンプルを作ってみます。今回作成するのは先ほどのUser
クラスとActionGame
クラスをそのままDIできるようにしていきます。
この場合は依存性の提供元はGameModuleクラス、依存性の要求先はUserクラスになるように実装していきます。
作成するファイル
- GameModule.kt
- AppComponent.kt
- ActionGame.kt
- User.kt
GameModule.kt
まずは依存性の提供方法を定義します。GameModule
クラスを定義し、中にはインスタンスを提供するためのメソッドを定義します。@Provides
アノテーションを付与することでDaggerに依存性を提供するためのメソッドであることを知らせます。
@Module
アノテーションでモジュールであることを知らせます。
import dagger.Module
import dagger.Provides
@Module
class GameModule {
@Provides
fun provideActionGame(): ActionGame {
return ActionGame()
}
}
AppComponent.kt
続いて依存の提供元と要求先を紐付けていきます。このインターフェースがDIを実装するためには欠かせない存在になり、またこれを元にDaggerもクラスを自動生成してくれます。
@Component
のmodules
属性に依存性の提供元を指定し、インターフェース内のメソッドで依存性を注入したい要求先をインスタンス化します。
import dagger.Component
@Component(modules = [GameModule::class])
interface AppComponent {
fun getUser():User
}
自動生成されるのはこの場合DaggerAppComponent
クラスです。Dagger + コンポーネント名
という規則でcom.パッケージ名(test)
内にDI
ディレクトリが作られその中に生成されます。自動生成されるタイミングはビルドされた時です。
ActionGame.kt
ActionGame
クラスは特に変更はありません。
class ActionGame {
val requiredStamina: Int = 10
}
User.kt
User
クラスはActionGame
クラスに依存しており、動作させるためにはActionGame
インスタンスを受け取る必要があります。Daggerを介してインスタンスを注入してもらうため@Inject
アノテーションを付与したconstructor
を用意しておきます。
import javax.inject.Inject
class User @Inject constructor(
private val actionGame: ActionGame
) {
var stamina: Int = 20
fun playGame() {
if (actionGame.requiredStamina <= stamina) {
println("Playing")
stamina -= actionGame.requiredStamina
} else {
println("Out of Stamina...")
}
}
}
MainActivity.kt
最後に実際に動作するのか試してみます。MainActivity
内で使用するには以下のように実装します。DaggerAppComponent
クラスはビルドしないと生成されないので先に「Rebuild Project」などを実行しておきます。
create
メソッドからDaggerAppComponent
インスタンスを取得し、getUser
メソッドからUser
インスタンスを取得できます。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val appComponent = DaggerAppComponent.create()
val user = appComponent.getUser()
user.playGame()
println("残りのスタミナ: ${user.stamina}")
}
}
ここではActionGame
が見当たりませんが、内部的にDaggerが注入してくれているので問題なく動作するはずです。
今回の全体は GitHubに上げているので参考にしてください。
フィールドインジェクション
実際にDagger2を使用してフィールドインジェクションを実装した小さなサンプルを作ってみます。今回作成するのはシステムによってインスタンス化されるMainActivity
クラスにMainViewModel
クラスをDIできるようにしていきます。よくあるMVVM構造を実装する際に活用できます。
おすすめ記事:【Swift】MVVMアーキテクチャとは?ViewModelの役割
MainViewModel.kt
class MainViewModel {
fun greet(): String {
return "こんにちわ"
}
}
ViewModelModule.kt
@Module
と@Provides
を使用してDaggerに依存性を提供するためのメソッドであることを知らせます。
@Module
class ViewModelModule {
@Provides
fun provideMainViewModel(): MainViewModel {
return MainViewModel()
}
}
MainActivityComponent.kt
依存の提供元と要求先を紐付けていきます。@Component
のmodules属性に依存性の提供元を指定します。コンストラクタの時とは異なり中に定義するメソッドは引数に依存先のインスタンスを受け取ります。
@Component(modules = [ViewModelModule::class])
interface MainActivityComponent {
fun inject(mainActivity: MainActivity)
}
MainActivity.kt
MainActivityではlateinit
を使用してプロパティへの格納タイミングを遅らせています。ここに@Inject
をつけることでDaggerが自動でインスタンスを格納してくれます。自動生成されたDaggerMainActivityComponent
からcreate().inject(this)
で自身を渡すことで依存性が注入されます。
class MainActivity : AppCompatActivity() {
@Inject lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerMainActivityComponent.create().inject(this)
val text: TextView = findViewById(R.id.text)
text.text = viewModel.greet()
}
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。