【Kotlin/Android】Hiltの導入方法と使い方!依存性注入(DI)とは?

【Kotlin/Android】Hiltの導入方法と使い方!依存性注入(DI)とは?

この記事からわかること

  • Android Studio/KotlinHilt使い方
  • DI(依存性注入)とは?
  • 導入方法仕組み

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

みんなの誕生日

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

posted withアプリーチ

環境

Hiltとは?

Hilt」とはAndroidアプリ開発においてオブジェクト同士の依存関係を解決し、プロジェクト内のオブジェクトに対して依存性注入(DI)デザインパターンを活用するためのライブラリです。Googleが管理するライブラリで内部的にはDaggerというライブラリを使っており、学習コストが高いDaggerをラップして使いやすくしてあるのがHiltになります。

依存関係とは?

そもそも依存関係とはオブジェクト同士が依存している関係のことです。ここでいうオブジェクトはクラスや構造体、メソッドなども当てはまります。例えば以下は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についての詳細は以下の記事を参考にしてください。

導入方法と前準備

公式リファレンス:Dependency injection with Hilt

Hiltを導入するためにはプロジェクトの「build.gradle」にkspとhiltの依存関係を追加します。


plugins {
    // 〜〜〜〜〜〜
    id 'org.jetbrains.kotlin.android' version '2.0.21' apply false
    id 'com.google.dagger.hilt.android' version '2.56.2' apply false
}

続いてモジュール側の「build.gradle」にもkspとhiltの依存関係を追加します。


plugins {
  id 'com.google.devtools.ksp'
  id 'com.google.dagger.hilt.android'
}

// 〜〜〜〜〜〜

dependencies {
  implementation "com.google.dagger:hilt-android:2.56.2"
  ksp "com.google.dagger:hilt-compiler:2.56.2"
}

これで依存関係の設定は完了です。次にプロジェクト内でHiltを利用できるようにしていきます。

Hiltを利用できるようにするために@HiltAndroidAppアノテーションが付与されたApplicationクラスを実装する必要があります。Applicationを継承したクラスを実装済みであれば@HiltAndroidAppアノテーションを付与し、なければ新規で継承したクラスを空でも良いので定義しておいてください。


@HiltAndroidApp
class App : Application()

Applicationサブクラスを定義したら忘れずに「AndroidManifest.xml」ファイルに追加しておいてください。


<application 
  android:name=".App">
  // 〜〜〜〜〜〜〜〜〜〜
</application>

Hiltを使用した実装例

Androidで依存関係インジェクションを行う主な方法は2つあります。

コンストラクタインジェクション

コンストラクタインジェクションはその名前の通りコンストラクタを使用して依存性を挿入する方法です。ViewModelやRepositoryなどシンプルな独自のクラスなどでは簡単にコンストラクタインジェクションを実装することができます。

フィールドインジェクション(セッターインジェクション)

ActivityやFragmentなどシステムによってインスタンス化されるクラスはコンストラクタインジェクションが不可能です。そのためクラスの作成後に依存関係がインスタンス化されます。

公式リファレンス:Android での依存関係インジェクション

コンストラクタインジェクション

例えばViewModelやRepositoryなど依存性注入(DI)を行うためには「コンストラクタインジェクション」という方法を用います。最初にRepositoryクラスのconstructorに対して@Injectアノテーションを付与します。引数でContextを受け取りたい場合などは@ApplicationContextを付与してあげてください。


class RootRepository @Inject constructor(
  @ApplicationContext private val context: Context,
) {
  // 実装〜〜〜〜〜〜
}

続いてViewModel側に@HiltViewModelアノテーションを付与し、こちらもconstructorに対して@Injectアノテーションを付与します。またViewModelはandroidx.lifecycle.ViewModelを継承させておきます。

おすすめ記事:【Kotlin/Android Studio】ViewModelの使い方!画面再構築のデータ保持


@HiltViewModel
class MainViewModel @Inject constructor(
    private val rootRepository: RootRepository
): ViewModel() {
  // 実装〜〜〜〜〜〜
}

最後にViewModelの呼び出し側(Activity)に@AndroidEntryPointを付与します。ViewModelのインスタンス化はviewModels()を使用します。


@AndroidEntryPoint
class MainActivity: AppCompatActivity() {
    private val viewModel: MainViewModel by viewModels()
    // 実装〜〜〜〜〜〜
}

これでコンストラクタインジェクションの実装は完了です。

フィールドインジェクション

コンストラクタを経由できないActivityやFragmentに依存性注入(DI)を行うためには「フィールドインジェクション」という方法を使用します。Repositoryの記載は先ほどと変わらずActivity側で@Injectアノテーションを付与するのがlateinitで定義した変数になります。

おすすめ記事:【Kotlin/Android】lateinit(遅延初期化プロパティ)とは?使い方


@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var rootRepository: RootRepository
}

lateinitを使用しているのでsuper.onCreateが実行された後でないと参照できないので注意してください。

@ActivityContextと@ApplicationContext

Contextを注入するためのアノテーションとして@ActivityContext@ApplicationContextがあります。両者の違いは「アプリ全体に依存したContext」か「Activityに依存したContext」かという違いです。

アノテーション 概要 使用用途
@ActivityContext Activityに依存したContext DB操作, DIのModule, Repositoryなど
@ApplicationContext アプリ全体に依存したContext Dialog, Toast, View関連のUI操作など

UIに絡む部分には@ActivityContextを使ってあげる必要がありますが、適切に解放しないとメモリリークを引き起こすので注意してください。

インターフェースで抽象度をあげる:@Module / @Provides

先ほどのRootRepositoryを共通のインターフェースを定義して継承させ、モックなどに切り替えやすくテストしやすい設計にしたいと思います。その場合先ほどのコンストラクタインジェクションのままだと期待通りに実装できないので@Module@Providesアノテーションを使用していきます。

まずはRootRepositoryのインターフェースを定義します。RootRepositoryと言う名前はインターフェースで使用したいので元のクラスはRootRepositoryImplへ変更します。


/** アプリ内で扱うPersonデータのRepositoryインターフェース */
interface RootRepository {
  fun fetchAllUser(callback: (List<Person>) -> Unit)
}

RootRepositoryImplRootRepositoryを継承させるくらいで特に変化はありません。


/** アプリ内で扱うPersonデータのRepository実態 */
class RootRepositoryImpl @Inject constructor(
  @ApplicationContext private val context: Context,
): RootRepository {
  override fun fetchAllUser(callback: (List<Person>) -> Unit) {
    // 〜〜〜〜〜〜
  }
}

肝となるのがRepositoryModuleクラスです。このモジュールクラスが実際に注入する依存関係を定義するクラスになっています。@Moduleアノテーションを付与して依存性提供の定義をする場所であることを示し、@Provides対象のインターフェースにどの実態を渡すかを定義します。


/**
 * Hilt に対して Repository 関連の依存関係をどのように提供するかを定義するクラス
 *
 * このモジュールはアプリケーション全体(Singletonスコープ)で有効
 * RootRepository(インターフェース)を要求されたときに、RootRepositoryImpl(実装)を注入することを定義
 */
@Module // Hiltに依存性提供の定義をする場所であることを示す
@InstallIn(SingletonComponent::class) // アプリ全体で共有する依存性(シングルトン)であることを示す
object RepositoryModule {

  @Provides // 対象のインターフェースにどの実態を渡すかを定義する
    fun provideRootRepository(
        impl: RootRepositoryImpl
    ): RootRepository = impl
}

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

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

Search Box

Sponsor

ProFile

ame

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

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

New Article