[Android]DI(依存性注入)ってなんか危ないことなのかなって思ったけど、怖いもの見たさで使ってみる(ゆるく解説)


DIとは?

どうもReoです。
DI(依存性注入)とは、危ないお薬を注射で、、、
DI(依存性注入)とは、あるオブジェクトから他のオブジェクトを使う関係を注入することを意味します。
そして、そちらを自動でやってくれるのがHiltというライブラリです。

リファレンスを一旦読んでみましょう。
https://developer.android.com/training/dependency-injection/hilt-android?hl=ja
元々DaggerというDIライブラリがあるみたいですね。
筆者は初めてDIを学んでいるので、Daggerのことは良く分かりません。
取り敢えず、DaggerはHiltの前身みたいなので、Hilt使えばいいかなと思いました。

リファレンスには、このように書いてあります。

Hilt は Android 用の依存関係インジェクション ライブラリです。これを使うことで、プロジェクトで依存関係の注入(DI)を手動で行うためのボイラープレートが減ります。

まずは、依存関係とは?についてコードで見てみましょう。

class Sonic {
     var km: Double = 0.0
     val action = Action()
     km = action.run(km)
}

class Action {
   fun run(distance: Double): Double = distance + 10.0
}

自分自身、DIというと凄く難しい概念を想像していました。
しかし、以下のように考えると割と簡単でし、ある程度Androidの学習をされている方でしたら一度は見たことある書き方だと思います。

最初に

DIとはあるオブジェクトから他のオブジェクトを使う関係を注入することを意味します

と表現しましたが、上記の例で例えるとSonicというオブジェクトからActionというオブジェクトを使う依存関係を注入している、つまりDIと換言することが出来ます。

ちなみに、こちらの記事を中心に読みながらこの記事を書いています。
https://qiita.com/MoriokaReimen/items/750644062b3f5bec18ed#gradle%E3%81%AE%E8%A8%AD%E5%AE%9A
ちなみに、この記事内にあるように、HiltにはField InjectionとConstructor Injectionの二つのDI手法をサポートしています。
先程、書いたソニックのコードがField Injection
そして、以下がConstructor Injection

class Sonic(private var action: Action) {
   var km: Double = 0.0
   km = action.run(km)
}

class Action() {
   fun run(distance: Double): Double = distance + 10.0
}

より感覚的に掴みたい方は、こちらの記事も見てみると良きかなと思います。
https://qiita.com/keidroid/items/7f0112502a08e2107c67

そんで?

この時点で、DIができているので、「なるほど。これが、DIか・・・。」と記事が終了してしまいそうになります。
しかし、これを自動化できるライブラリがあるので、そちらを解説して終了したいと思います。

使い方

導入

では、いつものようにプラグインをしていきましょう!
https://developer.android.com/training/dependency-injection/hilt-android?hl=ja#setup
ところで、リファレンスでは依存関係を追加すると表現しています。

あ、なるほど。

依存関係を追加するということは、それを使える関係を注入するということで、これもある意味ではDI(依存性注入)なのかぁー!と自分の中でひらめきがありつつ、Gradleの設定をしていきやす。

まずは、build.gradle(project)を開き

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.32-alpha'
    }
}

続いて、build.gradle(:app)

...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

android {
    ...
}

dependencies {
    implementation "com.google.dagger:hilt-android:2.32-alpha"
    kapt "com.google.dagger:hilt-android-compiler:2.32-alpha"
}

あとあと、HiltはJava8の機能を使うみたいです。
筆者は、デフォルトで設定されていたので追加する必要なかったのですが、そうでない人のために。
build.gradle(project)にて、以下を追加。

android {

   ...

   compileOptions {
      sourceCompatibility JavaVersion.VERSION_1_8
      targetCompatibility JavaVersion.VERSION_1_8
    }
}

Applicationクラスを作成

まずは、Applicationクラスを自作します。
そして、Hiltアノーテーションを付けます。

@HiltAndroidApp
class Myapplication : Application() {
      ...
}

これにより、このアプリはHiltで動きます!というのを教えました。

Manifestを変更

アプリケーションクラスが変更されたので、Manifestを開きapplicationタグ内の名前を変更

    <application
        android:name=".MyApplication" // ここを追加
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.DIPractice">
        <activity android:name=".MainActivity"> // こっちじゃないよ!(筆者間違えた…)
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

この設定によりアプリが起動するときに、一番最初にMyApplicationファイルが開かれます。
ここで、Hiltを提供しており、アプリケーションレベルでHiltにアクセスすることが可能になります。
リファレンスでは、

Hilt のコード生成をトリガーします

と表現していますね。(かっこいいけど、分かりにくっ…笑)
これにて、Hiltが使う準備完了です。

注入先を定義

方法はかなりシンプルです。

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
      ...
}

上記のアノーテーションを追加します。
これによって、MainActivityはHiltを使います!というのをHiltに対して教えることになります。

Hiltモジュールを作成

MainActivityにActionクラスを依存性注入しやす。
その為、以下のようにHiltモジュールを作る必要があります。

@InstallIn(SingletonComponent::class)
@Module
object ActionModule {
    @Provides
    fun provideAction(): Action = Action()
}

アノテーションの意味については、こちらの記事で解説しておりますので参照ください。
https://qiita.com/MoriokaReimen/items/750644062b3f5bec18ed#%E3%82%A2%E3%82%AF%E3%83%86%E3%82%A3%E3%83%93%E3%83%86%E3%82%A3%E3%81%AB%E5%85%B7%E8%B1%A1%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%92di%E3%81%99%E3%82%8B

@Moduleは、このクラスがHiltであることの証明
@Providesは、インスタンス生成時のメソッド
@InstallInは、インスタンスの寿命指定
SingleComponentは、アプリと寿命が一致(applicationContext的な!)

ってな感じですね!!

では、本題の依存性注入!!!

先程@AndroidEntryPointというアノーテーションを付けたMainActivityにて、

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
   @Injection lateinit var action: Action
   ...
}

@Injectionが注入という意味なので、actionをDIしていることがお分かりかと思います。
ちなみに、裏では先ほど@Providesにて定義した関数、provideAction()が呼び出されて、Actionインスタンス生成処理が行われています。

もう、@MoriokaReimenさんの輪唱みたいになっちゃいますが(笑)、この場合だとDIするたびにインスタンスを生成されてしまうので、それは冗長的だ!という場合には、以下のアノテーションを追加すると最初のインスタンス生成後は、同じインスタンスを使い回すことができます!

@InstallIn(SingletonComponent::class)
@Module
object ActionModule {
    @Provides
    @Singleton
    fun provideAction(): Action = Action()
}

確認

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
   @Injection lateinit var action: Action
   var km: Double = 0.0
   ...
   km = action.run(km)
   Log.d("degug-app","$Km")
}

これで、MainActivityクラスにActionクラスをDIできましたね。

終わりに

インターン先でHiltを用いるかもしれないとのことから、急遽Hiltを使ってみました。
まだ、Hiltの一部の機能しか使えてはいないものの、何とか記事に出来たのは良かったです。

参考記事

https://developer.android.com/training/dependency-injection/hilt-android?hl=ja

https://qiita.com/MoriokaReimen/items/750644062b3f5bec18ed#gradle%E3%81%AE%E8%A8%AD%E5%AE%9A

https://qiita.com/takahirom/items/46053e031041405e2a9e#dagger-hilt%E3%82%92videoplayer%E3%81%AE%E4%BE%8B%E3%81%A7%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86