이번 포스팅은 드디어 의존성 주입 (dependency injection, di) 라이브러리중 하나인 힐트를 알아보도록 하자!
우선 들어가기전 DI의 개념을 모르면 어려울 수도 있기 때문에 학습을 하고 이 글을 보는걸 추천한다.
DI에 대한 포스팅은 추후에 추가 하도록 하겠다.
# Hilt(힐트)란?
간단하게 정리하자면 안드로이드에서 쉽게 의존성을 관리해주고 주입해주는 라이브러리라고 생각하면 된다.
또한 기존에 많이 쓰이던 Dagger(대거)를 기반으로 만들어졌다.
대거는 러닝커브가 높은 편이라서 학습하기가 어렵다 그리고 구글에서도 힐트를 지원해주고 있어서 안쓸 이유가 없는거 같다.
# 사용법
## gradle 추가
- build.gradle
plugins {
...
id 'com.google.dagger.hilt.android' version '2.44' apply false
}
- app/build.gradle
...
plugins {
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
}
android {
...
}
dependencies {
implementation "com.google.dagger:hilt-android:2.44"
kapt "com.google.dagger:hilt-compiler:2.44"
}
// Allow references to generated code
kapt {
correctErrorTypes true
}
이렇게 build.gradle 두군데에 라이브러리와 플러그인을 추가 해준다.
## Application 클래스에 어노테이션 추가 및 Manifest 추가
이번에 할 작업은 Application 클래스에 @HiltAndroidApp라고 하는 어노테이션을 추가해주는 작업을 할 것이다.
만약 Application 클래스가 없다면 직접 만들어서 아래와 같이 추가 해준다.
package com.example.hiltex
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
// HiltAndroidApp 어노테이션 추가
@HiltAndroidApp
class HiltApplication : Application() {
}
<?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:name=".HiltApplication" // 만들어준 클래스 추가
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:supportsRtl="true"
android:theme="@style/Theme.HiltEx"
tools:targetApi="31">
이와 관련해 안드로이드 공식 문서에서는 아래와 같이 적혀 있다.
Hilt를 사용하는 모든 앱은 @HiltAndroidApp으로 주석이 지정된 Application 클래스를 포함해야 합니다.
@HiltAndroidApp은 애플리케이션 수준 종속 항목 컨테이너 역할을 하는 애플리케이션의 기본 클래스를 비롯하여 Hilt의 코드 생성을 트리거합니다.
생성된 이 Hilt 구성요소는 Application 객체의 수명 주기에 연결되며 이와 관련한 종속 항목을 제공합니다. 또한 이는 앱의 상위 구성요소이므로 다른 구성요소는 이 상위 구성요소에서 제공하는 종속 항목에 액세스할 수 있습니다.
## 액티비티에 어노테이션 추가( Android 클래스에 종속 항목 삽입 ) - 힐트 컴포넌트 생성
Application 클래스에 @HiltAndroidApp어노테이션을 추가해 줬으면 이제는 @AndroidEntryPoint라는 어노테이션을 아래와 같이 액티비티나 프래그먼트에 붙여서 힐트가 쓰인다는걸 알려주면된다.
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() { ... }
여기서 @AndroidEntryPoint 어노테이션은 Activity, Fragment 뿐만 아니라 다양한 클래스를 지원한다.

이렇게 어노테이션을 붙이면 액티비티 또는 다양한 클래스에서 아래와 같이 @Inject 어노테이션을 사용해 주입을 받을 수 있다.
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
@Inject lateinit var analytics: AnalyticsAdapter
...
}
## 생성자로 객체 주입 가능 (@Inject constructor 사용)
힐트는 생성자에서 객체 주입도 가능하다 아래 예시 처럼 @Inject 어노테이션을 붙이고 constructor로 받아올 객체를 추가해준다.
이번 예시로는 UseCase에서 Repository를 받아오는 예시를 보여주겠다.
class ExampleUseCase @Inject constructor(
private val exampleRepository: ExampleRepository
) {
suspend fun setData(): ExampleContent {
return exampleRepository.setData()
}
}
## 생성자에 주입 할 객체 모듈 생성
위 생성자로 객체를 주입 하기 위해서는 모듈을 반드시 생성해줘야 한다.
힐트에서 모듈 생성하는 방법은 2가지가 있다.
1. @Binds를 사용하여 인터페이스 삽입
2. @Provides를 사용하여 인스턴스 삽입
그리고 반드시 모듈은 모듈이라고 명시를 해줘야 하는데 @Module 어노테이션을 해당 모듈에 추가하면 된다.
위 두가지를 바로 위에 있는 UseCase예시를 통해 알아보자
1. @Binds를 사용하여 인스턴스 삽입
이 경우는 보통 인터페이스의 인스턴스를 제공해야 할 때 사용한다. 무슨말인지 이해가 안된다면 예시 코드를 보면 이해가 갈 것이다.
@Module
@InstallIn(SingletonComponent::class)
abstract class UseCaseModule {
@Binds
abstract fun bindsExampleUseCase(
exampleUseCaseImpl: ExampleUseCaseImpl
): ExampleUseCase
}
코드를 보면 처음 보는 어노테이션이 있는데 우선은 넘어가고 모듈 부분이 끝나면 다시 설명하겠다.
그래서 @Binds를 사용하는 경우는 abstract class와 abstract function으로 선언해야 하며 impl로 구현이 되었을때 사용 가능하다.
2. @Provides를 사용하여 인스턴스 삽입
이경우는 클래스가 외부 라이브러리에서 제공되서 클래스를 소유하지 않은 경우 사용 한다.
예를들어 Retrofit, OkHttpClient, Room DB 같은 클래스 또는 빌터 패턴으로 인스턴스를 생성하는 경우를 말한다.
이해가 안된다면 예시코드를 보면 이해가 갈 것이다.
@Module
@InstallIn(SingletonComponent::class)
object UseCaseModule {
@Singleton
@Provides
fun provideExampleUseCase(
exampleRepository: ExampleRepository
): ExampleUseCase{
return ExampleUseCase(exampleRepository)
}
}
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Singleton
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://example.com")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.build()
}
}
@Binds 어노테이션과는 다르게 선언시 object와 일반 function으로 선언한다.
이제 위에있는 어노테이션들을 알아보자!
### @InstallIn()
힐트의 컴포넌트를 지정해주는거라고 생각하면 된다.
@InstallIn() 괄호 안에 들어가는 컴포넌트는 아래와 같은 계층을 가진다.
사진을 보면 SingletonComponent가 최상위 컴포넌트 이고 각 컴포넌트들은 그 상위 컴포넌트로 부터 상속을 받을 수 있다.
또한 각 컴포넌트들은 아래 사진과 같은 생명주기를 가지고 있다.
각 컴포넌트마다 해당 생명주기를 잘 이해하면 사이드이펙트가 줄어들거 같다.

+ ApplicationContext 가 필요할 때
추가로 객체 생성시 ApplicationContext가 필요할 때가 있는데 그럴때는 아래 예시와 같이 모듈에 @Provides를 이용해서 @ApplicationContext를 추가해주면 사용 할 수 있다.
@Module
@InstallIn(SingletonComponent::class)
object ExampleModule {
@Singleton
@Provides
fun provideExample(
@ApplicationContext appContext: Context
): Example{
return Example(appContext)
}
}
끝으로 이번 포스팅을 하면서 힐트를 공부해봤는데 DI공부는 해도해도 부족한거 같다..
그리고 컴포넌트 생명주기를 잘이해해서 사용하는것도 중요해보인다.
다음 포스팅은 Hilt + CleanArchitecture를 이용한 간단한 프로젝트를 올려보도록 하겠다!!

참고
https://developer.android.com/training/dependency-injection/hilt-android?hl=ko#kotlin
https://developer88.tistory.com/349
'안드로이드 공부 노트' 카테고리의 다른 글
[Android] 안드로이드 인앱결제 관련 이론 정리 (0) | 2023.12.01 |
---|---|
[Android] 안드로이드 앱 아이콘 바꾸기, 앱 아이콘 이미지 적용법(런처 아이콘 변경) - App Icon Change (0) | 2023.11.06 |
[Android] DI(Dependncy Injection) 알아보기! (0) | 2023.02.05 |
[Android] 안드로이드 전화번호 포맷 맞춰주기 (전화번호 사이에 하이픈(-) 넣기) (2) | 2022.12.28 |
[Android] 안드로이드 Parcelable(@Parcelize) 사용해 액티비티간 데이터(Object) 전달하기 - Kotlin (0) | 2022.10.28 |