Dagger HILT와 Kotlin 그리고 Jetpack Compose
솔직히 말하자면, 처음 안드로이드에서 의존성 주입에 대해 들었을 때 "좋아, 내 삶을 더 복잡하게 만들 또 다른 것이구나"라고 생각했습니다. 안드로이드 개발은 이미 때때로 압도적으로 느껴지는데, DI는 또 다른 장애물처럼 보였습니다. 하지만 Dagger HILT를 한동안 사용한 후, 실제로는 일을 더 간단하게 만든다는 것을 깨달았습니다. 제가 여러분께 설명해드리겠습니다.
의존성이 대체 뭔가요?
멋진 것들로 들어가기 전에, 이것부터 명확히 합시다. 의존성은 그냥... 함수가 작동하기 위해 필요한 것들입니다. 그게 전부입니다. 이것을 보세요:
@Composable
fun MainScreen(viewModel: MyViewModel) {
// UI 코드
}
저 viewModel 보이시나요? 그게 의존성입니다. MainScreen은 작동하기 위해 그것이 필요합니다.
간단하죠.
왜 모든 것을 수동으로 전달할 수 없나요?
물론 할 수 있습니다! 그리고 작은 프로젝트의 경우, 그것은 완전히 괜찮습니다. 하지만 50개의 함수가 있고, 모두 같은 데이터베이스 인스턴스나 API 클라이언트가 필요하다고 상상해보세요. 이제 Retrofit에서 Ktor로 전환하기로 결정했다고 상상해보세요. 50개의 함수를 모두 업데이트하는 재미를 느껴보세요!
이것이 의존성 주입이 여러분의 정신을 구하는 곳입니다. 객체를 만드는 방법을 한 번 정의하면, HILT가 나머지를 처리합니다. 20개의 다른 곳에서 그 데이터베이스가 필요하신가요? 문제없습니다. 모든 곳에서 같은 인스턴스를 원하시나요? 쉽습니다.
Dagger HILT가 대체 뭔가요?
HILT는 기본적으로 의존성을 관리하는 여러분의 개인 비서입니다. 필요할 때 필요한 객체를 자동으로 생성하고 제공하는 라이브러리입니다. 전체 앱에서 공유되는 무언가의 단일 복사본을 원하시나요? HILT가 해결해줍니다. Activity가 살아있는 동안만 존재하는 무언가가 필요하신가요? HILT도 그것을 할 수 있습니다.
실제로 해봅시다
1단계: 의존성 추가하기
먼저, 프로젝트에 HILT를 추가해야 합니다. 저는 KAPT보다 빠르기 때문에 KSP를 사용하고 있지만, 둘 중 하나를 사용할 수 있습니다.
앱 레벨 build.gradle에:
dependencies {
implementation("com.google.dagger:hilt-android:2.48")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
ksp("com.google.dagger:hilt-android-compiler:2.48")
ksp("androidx.hilt:hilt-compiler:1.2.0")
}
plugins {
id("com.google.devtools.ksp")
id("com.google.dagger.hilt.android")
}
프로젝트 레벨 build.gradle에:
plugins {
id("com.google.dagger.hilt.android") version "2.48" apply false
id("com.google.devtools.ksp") version "1.9.0-1.0.13" apply false
}
2단계: 모듈 생성하기
모듈은 HILT에게 객체를 만드는 방법을 알려주는 곳입니다. 레시피 책이라고 생각하세요. "di"(의존성 주입의 약자)라는 패키지를 만들고 AppModule이라는 객체를 추가하세요:
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
// 의존성 레시피가 여기에 들어갑니다
}
@Module은 HILT에게 "이봐, 여기가 내 의존성을 정의하는 곳이야"라고 말합니다.
@InstallIn은 범위를 정의합니다 — 이 객체들이 얼마나 오래 살아야 하는지.
범위 이해하기
- SingletonComponent::class — 전체 앱 수명 동안 하나의 인스턴스
- ActivityComponent::class — Activity가 살아있는 동안만 존재
3단계: 의존성 제공하기
이제 실제 코드를 작성해봅시다. Room 데이터베이스가 필요하다고 가정해봅시다:
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun provideDatabase(
@ApplicationContext context: Context
): TaskDatabase {
return Room.databaseBuilder(
context,
TaskDatabase::class.java,
"task_database"
).build()
}
}
저 @ApplicationContext는 정말 편리합니다 — HILT가 수동으로 전달할 필요 없이
자동으로 앱 컨텍스트를 제공합니다.
Retrofit을 사용한 또 다른 예제입니다:
@Provides
@Singleton
fun provideApi(): MyAPI {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(MyAPI::class.java)
}
4단계: ViewModel에 주입하기
ViewModel은 약간 다르게 작동합니다. @HiltViewModel로 주석을 달고
@Inject constructor()를 사용합니다:
@HiltViewModel
class TaskViewModel @Inject constructor(
private val taskRepository: TaskRepository,
private val context: Context
) : ViewModel() {
// ViewModel 로직
}
생성자에 넣는 것은 무엇이든 AppModule에서 제공해야 합니다. HILT가 자동으로 모든 것을 연결합니다.
5단계: Application 클래스 설정하기
새 파일을 만드세요 (저는 보통 MyApp.kt라고 부릅니다):
@HiltAndroidApp
class MyApp : Application() {
// 그게 전부입니다! HILT가 나머지를 처리합니다
}
그런 다음 AndroidManifest.xml에 추가하세요:
<application
android:name=".MyApp"
android:allowBackup="true"
...>
6단계: MainActivity에 주석 달기
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 코드
}
}
7단계: 의존성 사용하기
Activity에 직접 무언가를 주입해야 하는 경우 (솔직히 말하면, 대부분 ViewModel을 사용하겠지만), 이렇게 할 수 있습니다:
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject
lateinit var taskViewModel: TaskViewModel
@Inject
lateinit var categoryViewModel: CategoryViewModel
}
솔직한 제 생각
처음에는 이것이 많아 보인다는 것을 압니다. 제가 시작했을 때, 저는 "왜 그냥 일반적인 방법으로 객체를 만들 수 없지?"라고 생각했습니다. 하지만 여러 화면, 데이터베이스, API 호출 등이 있는 실제 프로젝트에서 작업하면, HILT를 감사하게 될 것입니다.
배우는 가장 좋은 방법은 실제로 사용하는 것입니다. 작게 시작하세요 — 아마도 데이터베이스나 간단한 리포지토리만 주입하세요. 어떻게 작동하는지 보면, 이것 없이 어떻게 살았는지 궁금해질 것입니다.
저를 믿으세요, 미래의 여러분은 지금 이것을 배운 현재의 여러분에게 감사할 것입니다.
블로그 목록