はじめに
この記事では、Android開発において用いられる依存性注入ライブラリのHiltについて自分の理解をまとめておきます。
初学者ですので、記事の内容には誤りが含まれる可能性がありますがコメントで指摘していただけると幸いです。
Hiltの公式ドキュメントはこちら↓
https://developer.android.com/training/dependency-injection/hilt-android?hl=ja
依存性注入(DI)の概要
依存性注入(Dependency Injection)とは、クラスが必要とするオブジェクトを外部から注入することでコードの柔軟性を向上させる手法です。
DIのメリットとして、コードが変更に強くなるという点が挙げられます。(この点については後ほど説明します。)
依存性の例
例として、UserViewModel
というViewModelクラスがUserRepository
というリポジトリクラスを利用してユーザー情報を取得するというケースを仮定します。
class UserRepositoryImpl {
fun getUserName(): String {
return "UserName"
}
}
class UserViewModel : ViewModel() {
// 直接UserRepositoryに依存している
private val userRepositoryImpl = UserRepositoryImpl()
fun fetchUserName(): String {
return userRepositoryImpl.getUserName()
}
}
このコードにおいては、UserViewModel
の中でUserRepositoryImpl
クラスのインスタンスを生成しています。これがUserViewModel
クラスが直接的にUserRepositoryImpl
クラスに依存している状態になります。この場合、UserRepositoryImpl
クラスが変更されるとき、UserViewModel
側のコードも修正する必要が出てきます。
具体例としてリポジトリをRemoteUserRepository
とLocalUserRepository
に分けたくなったときを考えると、以下のようにUserViewModelを修正する必要があります。
class UserViewModel : ViewModel() {
private val userRepositoryImpl = RemoteUserRepository()
fun fetchUserName(): String {
return userRepositoryImpl.getUserName()
}
}
ここで、この依存関係を外部から注入するように変更してみます。
まずはUserRepository
というインタフェースを用意し、ここで宣言されたメソッドをUserRepositoryImpl
クラスで実装するという形に設計を変更します。
interface UserRepository{
fun getUserName(): String
}
class UserRepositoryImpl : UserRepository{
override fun getUserName(): String {
return "UserName"
}
}
そして、UserViewModel
はインタフェースであるUserRepository
に依存し、UserRepositoryImpl
の具体的な実装には依存しないようにします。
class UserViewModel(private val userRepository: UserRepository):ViewModel() {
fun fetchUserName(): String {
return userRepository.getUserName()
}
}
上記のコードには、上で紹介したUserViewModel
がUserRepositoryImpl
に直接依存しているケースと比較して以下のような変更点があります。
DIのメリット
DIのメリットとしては実装の変更がしやすくなるということが挙げられます。例えば、UserRepositoryImpl
が以下のようなLocalUserRepository
に変更されたとします。
class LocalUserRepository : UserRepository {
override fun getUserName(): String {
return "LocalUserName"
}
}

このケースではインタフェースであるLocalUserRepository
を実装するクラスが変更されますが、UserViewModel
側ではUserRepository
を引数として受け取るままでよく、コードに変更を加える必要性はありません。
Hiltの導入
それでは実際にHiltを使って依存関係を注入していきます。
まずProjectレベルのbuild.gradleに以下の内容を追加します。
plugins {
・・・
id("com.google.dagger.hilt.android") version "2.51.1" apply false
}
また、Moduleレベルのbuild.gradleに以下の内容を追加します。
plugins {
id("kotlin-kapt")
id("com.google.dagger.hilt.android")
}
・・・
dependencies{
・・・
implementation("com.google.dagger:hilt-android:2.51.1")
kapt("com.google.dagger:hilt-android-compiler:2.51.1")
}
次にApplicationクラスに@HiltAndroidApp
というアノテーションを付与します。
Applicationクラスを作っていない場合は、以下のように空でよいのでApplication
クラスを作成します。
@HiltAndroidApp
class SampleApplication : Application()
ここからは上の例で挙げている、「UserViewModel
クラスがUserRepository
インタフェースに依存し、その実装をUserRepositoryImpl
が行っている」という例を用いて説明していきます。
依存関係の注入を行うためには、UserRepository
のインスタンスをUserViewModel
に渡すとき、そのUserRepositoryの具体的な実装(どのインスタンスを渡せばよいのか)をHiltに伝える必要があります。
今回はUserRepositoryの依存関係をModule.ktというファイルに記述することにします。
@Module
@InstallIn(SingletonComponent::class)
object Module{
@Provides
@Singleton
fun provideUserRepository(): UserRepository {
return UserRepositoryImpl() // 実際の実装クラスを返す
}
}
@Module
はHilt に依存関係の提供方法を教えるためのクラスであることを示します。
@InstallIn(singletonComponent::class)
はモジュールがアプリ全体(SingletonComponent)に適用されることを意味します。
また、@Provides
はprovideUserRepository
関数が具体的な実装方法を提供するメソッドであることを示しています。さらに@Singleton
は提供される UserRepository
のインスタンスがアプリ全体で1つだけ存在するようにすることを保証するものです。
次にActivityクラスに@AndroidEntryPoint
アノテーションを付与します。依存関係の注入を行うには注入を行いたいクラスとその上位のコンポーネントにアノテーションを付与する必要があるためです。
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
・・・
}
そしてUserViewModelクラスに@HiltViewModel
アノテーションを付与、さらにコンストラクタに @Inject
を付与します。
@HiltViewModel
class UserViewModel @Inject constructor(
private val userRepository: UserRepository
) : ViewModel() {
・・・
}
これにより、Hilt が UserRepository の依存関係を解決し、UserViewModel に注入することができます。