〈kotlin〉依存関係インジェクション(DI) Hilt編

Android studioでkotlinを使ってAndroidアプリ作成の勉強中。
DIという単語が出てきて何?と思って導入の仕方まで勉強してみた記録の続き。
〈kotlin〉依存関係インジェクション(DI) - ゆるプログラミング日記

Hiltとは

DIを行うための Jetpack の推奨ライブラリ。
DIを手動で行おうとすると依存関係を取得するためのコンテナクラスだったり、
インスタンスを作成するためのFactoryだったり、
ライフサイクルを自分で管理したりとにかく大変そう。
Manual dependency injection  |  Android Developers

Hilt はプロジェクト内のすべての Android クラスにコンテナを提供し、
そのライフサイクルを自動で管理してくれる。
HIltのもとになっているDaggerライブラリでは、
コンテナやFactoryを自動で生成するために
コンポーネントという依存関係を示すグラフを作成する必要があったが、
Hiltではコンポーネントも自動で作成する。

build.gradle

//root
buildscript {
    ...
    dependencies {
        ...
        classpath("com.google.dagger:hilt-android-gradle-plugin:2.38.1")
    }
}

//app
plugins {
    kotlin("kapt")
    id("dagger.hilt.android.plugin")
}

dependencies {
    implementation("com.google.dagger:hilt-android:2.38.1")
    kapt("com.google.dagger:hilt-android-compiler:2.38.1")
}

Hiltアプリケーションクラス

Applicationクラスに@HiltAndroidApp アノテーションをつけることで、
アプリケーションレベルでコンポーネントが利用可能になる。

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

上記でHilt をセットアップした後、
依存関係を提供するために他のクラスには@AndroidEntryPointアノテーションをつける。
(現在サポートされているクラスはActivity, Fragment, View, Service, BroadcastReceiver, ViewModel。
ただしViewModelは@HiltViewModel。)

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

依存関係を取得するには、@Inject アノテーションを使用してフィールド インジェクションする。

依存関係の提供方法を指示

フィールドインジェクションしたクラスのインスタンスをHiltが作成できるようにするには、
@Injectアノテーションを使用してインスタンスの作成方法を指示する必要がある。

class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }

@Injectアノテーションをコンストラクタにつける。この時constructorと明記する。
クラスがコンストラクタを持っていなくても@Inject constructor()をつける。
これによりHiltはクラスのインスタンスの作り方を知ることができる。

コンストラクタがある場合、さらにそのインスタンス(上記だとAnalyticsServiceのインスタンス)が必要なので、
@Injectアノテーションをつけて作成方法を指示していく。
ただし、必要なインスタンスがインターフェースや外部ライブラリのクラスなどの場合、
コンストラクタインジェクションができないため後述のモジュールを定義する。

Hiltモジュール

モジュールはインターフェースや外部ライブラリのインスタンスの作成方法をHiltに伝えるためのクラス。
@Module アノテーションをつけたクラスを用意する。

@Binds
インターフェースインスタンスを注入する場合は
@Bindsアノテーションをつけた抽象関数を用意する。
関数の戻り値でインターフェースのインスタンスを知らせ、
関数のパラメータに実際に提供する実装を知らせる。

interface AnalyticsService {
  fun analyticsMethods()
}

//インターフェースを使った実際の実装
class AnalyticsServiceImpl @Inject constructor(
  ...
) : AnalyticsService { ... }

@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {

  @Binds
  abstract fun bindAnalyticsService(analyticsServiceImpl: AnalyticsServiceImpl): AnalyticsService
}

@Provides
外部ライブラリ(Retrofit, OkHttpClient, Roomなど)のインスタンスを注入する場合は、
@Providesアノテーションをつけた関数を用意する。
関数の戻り値で提供したい型のインスタンスを知らせ、
関数の中身にインスタンスの作成方法を記述する。

@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService( ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

@InstallIn
フィールド インジェクションができるAndroidクラスには、それぞれ関連付けされたHiltコンポーネントがあり、
それを@InstallInアノテーションで参照できる。
コンポーネントはそれぞれのAndroidクラスのライフサイクルに従って
作成・破棄されたり、スコープを設定したりできる。)
モジュールに@InstallInをつけて参照するコンポーネントを設定することで、
モジュール内のインスタンスがどのコンポーネントに提供されるか指定する。



ここまでで一応Hiltの導入ができる。
参考
Dependency injection with Hilt  |  Android Developers