Hiltってなんだ
- DIをするためのライブラリ
-
DIについての説明は省略
- DI ≠ (DIP)依存性逆転の原則
Hiltの導入方法(ざっくり)
-
-
kapt
ではなくksp
にすると良い
-
-
MainActivity
に@AndroidEntryPoint
アノテーションを付ける@AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { Multi_module_test_theme { val navController = rememberNavController() CompositionLocalProvider(LocalNavController provides navController) { MyAppNavHost(navController) } } } } }
-
MyApplication
を作成し、@HiltAndroidApp
というアノテーションを付ける@HiltAndroidApp class MainApplication: Application()
-
Androidマニフェストにそのことを記述する
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Multi_module_test" android:name=".MainApplication" //ここに記述 tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true" android:label="@string/app_name" android:theme="@style/Theme.Multi_module_test"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Hiltの基本的な使い所4つ
-
ViewModelとCompose(Fragment/Activity)のDI
-
@HiltViewModel
アノテーションをViewModelにつける - Composeではコンストラクタに
viewModel: HogeViewModel = hiltViewModel()
を入れる- 要
hilt-navigation-compose
- 要
- 依存関係を自動で注入してくれる
@Composable fun HomeScreen( viewModel: HomeScreenViewModel = hiltViewModel(), ) { val viewState by viewModel.viewState.collectAsStateWithLifecycle() when (val state = viewState) { is HomeScreenViewState.Loading -> { LoadingState() } is HomeScreenViewState.GridDisplay -> { Log.d("HomeScreen", "viewState in GridDisplay: $state") val characters = state.characters.collectAsLazyPagingItems() CharacterGrid(characters = characters) } is HomeScreenViewState.Error -> { val errorMessage = state.errorMessage Text(text = "Error: $errorMessage") } } } @HiltViewModel class HomeScreenViewModel @Inject constructor(): ViewModel() {}
-
-
ViewModelとRepository(UseCase)のDI
- ViewModelのコンストラクタに
@Inject
アノテーションを付ける - Repository(UseCase)をコンストラクタ引数として渡す
- HiltがRepositoryのインスタンスを生成し、ViewModelに注入
@HiltViewModel class HomeScreenViewModel @Inject constructor( private val repository: CharacterRepository, ) : ViewModel() { private val _viewState = MutableStateFlow<HomeScreenViewState>(HomeScreenViewState.Loading) val viewState: StateFlow<HomeScreenViewState> = _viewState.asStateFlow() init { loadCharacters() } private fun loadCharacters() { viewModelScope.launch { try { val pagingSource = repository.getCharacterPagingSource() _viewState.update { HomeScreenViewState.GridDisplay( characters = Pager( config = PagingConfig(enablePlaceholders = false, pageSize = 20), pagingSourceFactory = { pagingSource } ).flow.cachedIn(viewModelScope) ) } } catch (e: Exception) { _viewState.update { HomeScreenViewState.Error(e.message ?: "Unknown error") } } } } }
- ViewModelのコンストラクタに
-
RepositoryインターフェイスとRepositoryImpl(実装クラス)のDI (RepositoryModule)
-
@Binds
アノテーションを使用。 -
@Module
と@InstallIn
アノテーションが付いた抽象クラス内で、@Binds
アノテーションを付けた抽象メソッドを定義。 - メソッドの引数で実装クラスを指定し、戻り値でインターフェースを指定。
-
@Provides
よりも@Binds
の方が効率的(インスタンス生成が不要なため)。 -
bindHoge
という名前をつけがち
@Module @InstallIn(SingletonComponent::class) abstract class RepositoryModule { @Singleton @Binds abstract fun bindRepository( repositoryImpl: CharacterRepositoryImpl, ): CharacterRepository }
-
-
APiClient(
Retrofit
やKtor
など)のDI(NetworkModule)-
@Provides
アノテーションを使用。 -
@Module
と@InstallIn
アノテーションが付いたクラス内で、@Provides
アノテーションを付けたメソッドを定義。 - メソッド内で
APIClient
のインスタンスを生成して返す。 - 必要に応じて、
OkHttpClient
などの設定も行う。 - Singletonスコープにすることが多い。
-
provideHoge
という名前をつけがち
-
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideHttpClient(): HttpClient {
return HttpClient(OkHttp) {
defaultRequest { url(BASE_URL) }
install(Logging) {
logger = Logger.SIMPLE
}
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
})
}
}
}
@Provides
@Singleton
fun provideKtorClient(httpClient: HttpClient): KtorClient {
return KtorClient(httpClient)
}
}
// Retrofitの例
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
// ... OkHttpClientの設定 ...
.build()
}
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://example.com/")
.client(okHttpClient) // Dagger/HiltがOkHttpClientを自動的に注入
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
使ってみたメリット
- NavHostがスッキリ書けるため嬉しい
@Composable
fun MyAppNavHost(
navController: NavHostController = rememberNavController(),
) {
NavHost(
navController = navController,
startDestination = HomeScreenRoute
) {
homeScreen()
characterDetailsScreen()
characterEpisodeScreen()
}
}
- ボイラープレートコードを記述しなくて良い
- ViewModel Factoryとかその辺を書かなくて良くなる
やってて詰まった点(アノテーションの意味と使い所)
1. @Module
-
意味:
- このクラスがDagger/Hiltのモジュールであることを示します。
- モジュールは、依存オブジェクトの提供方法を定義する場所です。
- Hiltは、
@Module
アノテーションが付いたクラスを見つけて、そこから依存関係の情報を取得します。
-
使いどころ:
- 依存オブジェクトの生成ロジック(
@Provides
メソッド)や、インターフェースと実装クラスのバインディング(@Binds
メソッド)を記述するクラスに付けます。 - 通常、
object
またはabstract class
として定義します。-
object
:@Provides
メソッドのみを含むモジュールに適しています。 -
abstract class
:@Binds
メソッドを含むモジュール、または@Binds
と@Provides
を組み合わせたモジュールに適しています。
-
- 依存オブジェクトの生成ロジック(
2. @Provides
-
意味:
- このメソッドが依存オブジェクトを提供することを示します。
-
@Provides
メソッド内で、依存オブジェクトを生成し、return
します。 - メソッドの戻り値の型が、提供される依存オブジェクトの型になります。
- メソッドの引数には、依存オブジェクトの生成に必要な他の依存オブジェクトを指定できます(Dagger/Hiltが自動的に注入します)。
-
使いどころ:
- 外部ライブラリのインスタンス(Retrofit, OkHttpClient, Room Databaseなど)を提供する。
- 複雑な初期化ロジックが必要なオブジェクトを提供する。
- インターフェースに対して複数の実装が存在し、条件によって使い分けたい場合。
-
@Module
アノテーションが付いたクラス(object
またはclass
)内に記述します。
Kotlin
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
// ... OkHttpClientの設定 ...
.build()
}
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { // OkHttpClientはHiltが自動的に注入
return Retrofit.Builder()
.baseUrl("https://example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
3. @Singleton
-
意味:
- このアノテーションが付いた依存オブジェクトが、アプリケーション全体で1つだけ生成され、共有されることを示します(Singletonスコープ)。
-
@Provides
メソッドや、@Inject
が付いたコンストラクタに付けることができます。
-
使いどころ:
- アプリケーション全体で同じインスタンスを使い回したいオブジェクト(データベース接続、ネットワーククライアント、設定オブジェクトなど)に付けます。
4. @InstallIn(Component::class)
-
意味 (Hilt専用):
-
@Module
アノテーションと一緒に使用します。 - このモジュールが、どのコンポーネントにインストールされるか(どのスコープで有効になるか)を指定します。
-
Component::class
には、Hiltが提供する定義済みのコンポーネントクラスを指定します。-
SingletonComponent::class
: アプリケーション全体 (Singleton) -
ActivityComponent::class
: Activity -
FragmentComponent::class
: Fragment -
ViewModelComponent::class
: ViewModel -
ServiceComponent::class
: Service -
ViewComponent::class
: View (カスタムViewなど) -
ViewWithFragmentComponent::class
: Fragment内のView
-
- どのコンポーネントにインストールするかによって、提供される依存オブジェクトのスコープ(生存期間)が決まります。
-
-
使いどころ:
-
@Module
アノテーションが付いたクラスに必ず付けます。
-
5. @Binds
-
意味:
- インターフェースとその実装クラスを関連付ける(バインドする)ために使用
- 抽象メソッドに
@Binds
アノテーションを付け、戻り値の型をインターフェース、引数の型を実装クラスにs - Dagger/Hiltは、
@Binds
メソッドの情報を元に、インターフェースが要求された場合に、どの実装クラスのインスタンスを提供すればよいかを判断 -
@Binds
メソッドは抽象メソッドなので、実装はDagger/Hiltが自動生成
-
使いどころ:
- インターフェースと実装クラスが1対1で対応する場合に使用
-
@Provides
を使うよりも@Binds
を使う方が効率的(インスタンス生成のオーバーヘッドがないため) -
@Module
アノテーションが付いた抽象クラス内で使用
アノテーション | 役割 | 使いどころ |
---|---|---|
@Module | このクラスがDagger/Hiltのモジュールであることを示す。依存オブジェクトの提供方法を定義する場所。 | 依存オブジェクトの生成ロジック(@Provides)やバインディング(@Binds)を記述するクラスに付ける。通常は object (Kotlin) または abstract class。HogeModuleみたいな名前のファイルAPIクライアントやRepositoryのDIのときに用いる |
@Provides | このメソッドが依存オブジェクトを提供することを示す。メソッド内で依存オブジェクトを生成して return する。 | 外部ライブラリのインスタンスや、複雑な初期化が必要なオブジェクトの提供、インターフェースに対して複数の実装が存在し条件によって使い分けたい場合に利用。@Module が付いたクラス内に記述。 |
@Singleton | このアノテーションが付いた依存オブジェクトが、アプリケーション全体で1つだけ生成され、共有されることを示す(Singletonスコープ)。 | アプリケーション全体で同じインスタンスを使い回したいオブジェクト(データベース接続、ネットワーククライアントなど)に付ける。@Provides メソッドや、@Inject が付いたコンストラクタに付ける。 |
@InstallIn(Component::class) | (Hilt専用) @Moduleと一緒に使用。モジュールをどのコンポーネントにインストールするか(どのスコープで有効にするか)を指定する。SingletonComponent::class はアプリケーション全体で共有されるシングルトンコンポーネントを指定。 | @Module アノテーションが付いたクラスに必ず付ける。 |
@Binds | インターフェースとその実装クラスを関連付ける。抽象メソッドに @Binds を付け、戻り値の型をインターフェース、引数の型を実装クラスにする。Dagger/Hiltが実装クラスのインスタンスを提供。 | インターフェースと実装クラスが1対1で対応する場合に使用。@Provides よりも効率的。@Module アノテーションが付いた抽象クラス内で使用。 |