【Kotlin/Android Studio】Dagger2の導入方法と使い方!DIとは?

【Kotlin/Android Studio】Dagger2の導入方法と使い方!DIとは?

この記事からわかること

  • Android Studio/KotlinDagger2使い方
  • DI(依存性注入)とは?
  • 導入方法仕組み
  • アノテーション種類
  • @Inject@Module@Provides@Componentの使い方と役割

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

参考文献:公式リファレンス:Daggerの基本
参考文献:Dagger

環境

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"

}

追加した箇所と項目

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

まずは依存性の提供方法を定義します。GameModuleクラスを定義し、中にはインスタンスを提供するためのメソッドを定義します。@Providesアノテーションを付与することでDaggerに依存性を提供するためのメソッドであることを知らせます。

@Moduleアノテーションでモジュールであることを知らせます。


import dagger.Module
import dagger.Provides

@Module
class GameModule {
    @Provides
    fun provideActionGame(): ActionGame {
        return ActionGame()
    }
}

AppComponent.kt

続いて依存の提供元と要求先を紐付けていきます。このインターフェースがDIを実装するためには欠かせない存在になり、またこれを元にDaggerもクラスを自動生成してくれます。

@Componentmodules属性に依存性の提供元を指定し、インターフェース内のメソッドで依存性を注入したい要求先をインスタンス化します。


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()
    }
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index