개발/안드로이드

[Hilt] Android에서 Hilt란? (24-07-31 내용 보충)

스몰스테핑 2024. 7. 31. 16:57

Hilt란?

 

프로젝트에서 종속 항목 수동 삽입을 실행하는 상용구를 줄이는 Andorid용 종속 항목 삽입 라이브러리이다. 종속 항목 삽입(DI)를 실행하려면 모든 클래스와 종속 항목을 수동으로 구성하고 컨테이너를 사용하여 종속 항목을 재사용 및 관리해야 한다.

 

Hilt는 프로젝트의 모든 Android 클래스에 컨테이너를 제공하고 수명 주기를 자동으로 관리함으로써 애플리케이션에서 DI를 사용하는 표준 방법을 제공한다. Google의 Dagger를 기반으로 빌드되었기 때문에 Dagger가 누리는 이점인 컴파일 시간 정확성, 런타임 성능, 확장성 및 Android Studio 지원을 누릴 수 있다.

 

Hilt의 등장 배경

Hilt 이전에는 개인이나 회사별로 수동으로 별도의 DI 솔루션을 만들어서 사용했다.
널리 알려진 JAVA 외부 라이브러리를 사용하기도 했다.

안드로이드 팀은 최초의 개발언어를 JAVA로 선택했고, 의존성 주입 또한 JAVA의 방식을 따랐다.
JSR이란 것은 JAVA 플랫폼에 대한 스펙을 제한하고 규격화한 표준 사양을 말한다.

JSR 330 스펙은 JAVA를 위한 의존성 주입 표준 방법을 정의한 내용이다.
대부분의 JAVA 의존성 주입 라이브러리들은 JSR 330 스펙을 충족시키고 있다.

Hilt 또한 마찬가지이며, Annotation @Inject를 사용하는 것이 대표적이다.

 

DI 솔루션 변천사 (for Android)

1. Guice
- 자바를 위한 의존성 주입
- 런타임에 의존성 주입
- 리플렉션 기반

이 라이브러리는 단점이 있다. 리플렉션을 사용한다는 것인데, 이를 사용하면 런타임에 로드된 다른 클래스 정보들을 가져올 수 있는 장점이 있지만,
비용이 매우 큰 작업이기 때문에 앱의 퍼포먼스가 매우 낮아진다는 단점이 존재했다.

2. Dagger 2
- 자바를 위한 의존성 주입
- 컴파일 타임 코드 생성
- Square에서 최초 개발, 이후 구글에서 유지보수

이후 리플렉션을 사용하지 않고 앱의 퍼포먼스를 보장하는 라이브러리가 Dagger 2이다.
Hilt 등장 이전에 퍼포먼스가 보장되기에 가장 선호되는 DI 솔루션 라이브러리였다.

3. Koin
- 코틀린을 위한 의존성 주입
- 리플렉션 사용
- 서비스 로케이터 패턴

이후 안드로이드가 Kotlin을 지원하게되면서 Koin도 사용할 수 있게 되었으나, 이 Koin 또한 리플렉션을 사용하기 때문에 퍼포먼스가 떨어진다는 문제점이 존재했다.

4. Hilt
- 2020년 6월 최초 출시
- 안드로이드만을 위한 솔루션
- Dagger2 기반
- 표준 컴포넌트 제공

마침내 Hilt가 출시하게 되고, 안드로이드만을 위한 DI 솔루션이다.

 

Hilt를 사용하는 이유는?

기존에 종속 항목 삽입(DI)에 대해서 다룬 포스터에서도 느낀점이지만 프로젝트에 DI를 도입하면 코드가 유연해지고 테스트가 쉬워지는 즉, 유지보수성이 좋아지지만 설계가 어려워지고 코드를 파악하기 어려워 진다는 점이 존재했다.

 

Hilt도 마찬가지이긴 하지만,  Hilt는 기존에 개발자가 직접 작성했어야 하는 코드를 생성하여 Android 애플리케이션에서 수동 DI 또는 서비스 로케이터 패턴을 사용해야 하는 불필요한 상용구를 삭제할 수 있다.

 

별개로 Koin이라는 Hilt보단 좀 더 쉬운 DI 라이브러리도 존재하지만, 런타임에 서비스 로케이팅을 통해 인스턴스를 동적으로 주입해주기 때문에 런타임 퍼포먼스가 떨어지고, 리플렉션을 이용하기 때문에 성능 상 좋지 못하다고 한다.

 

Hilt는 Koin에 비해 어렵고, 컴파일 시 stub 파일을 생성하며 그래프를 구성하기에 빌드 시간도 Koin에 비해 상대적으로 느리지만, AndroidX 라이브러리들과 호환되며, Android Component별로 Scope가 명확하다는 점 때문에 Koin보다 Hilt를 사용하는 추세라고 한다.

 

물론 Hilt와 Koin 둘 다 장단점이 명확하기에 프로젝트의 규모와 서비스 방향에 따른 앱 구성에 따라 라이브러리를 선택하는게 옳을 것이다.

 

++

Koin과 Dagger2의 비교
Kotlin First를 외치거나, 어려운 설정 방법을 피하는 개발자 진영에서는 Koin을 선호한다.
하지만, Dagger 2와 Koin만 비교해봐도 안드로이드쪽에서는 주류가 되기 힘든 것이 Koin이다.

Koin은 Dagger 2처럼 Annotation 기반으로 코드 생성을 하지 않기에 의존성 그래프 체크를 런타임에 하게 된다.
런타임에 하게 된다는 것은 런타임 중 크래쉬가 발생해 사용자가 앱을 사용 중 뻗어버리는 경우를 볼 수 있다는 것이다.
또한, 위에서 말했듯이 리플렉션을 사용하기 때문에 Dagger 2보단 퍼포먼스가 떨어진다는 단점도 있다.
게다가 Koin이 시간의 흐름에 따라 앱 프로젝트의 규모가 점점 커졌을 때 의존성 그래프를 파악하기 어려워진다는 단점이 존재한다.

Koin 등장 이후에도 Dagger 2가 많은 사랑을 받은 이유가 이것이다.

  Hilt(Dependency Injection) Koin(Service Locator)
종속성 일부 핵심 클래스에 종속성 주임 모든 클래스가 Service Locator에 종속
호출방법 처음 한번만 호출(명시적인 호출이 없음) inject를 직접 호출(명시적인 호출)
의존 관계 의존 관계 파악이 쉬움 의존 관계 파악이 어려움

 

 

Hilt의 장점

1. Dagger2를 기반으로 안드로이드를 위한 표준화된 의존성 주입 방법 제공
2. 보일러 플레이트 감소
3. Jetpack과의 통합
4. 테스트 도구 제공
5. 마이그레이션 API 제공

 

Hilt의 컴포넌트 계층

위에서 잠깐 집고 넘어갔던 것 처럼 Hilt에서는 Android 환경에서 표준적으로 사용되는 컴포넌트들을 기본적으로 제공한다. 또한, 내부적으로 제공하는 컴포넌트들의 전반적인 생명주기 또한 자동으로 관리하기에 사용자가 초기 DI 환경을 구축하는데 드는 비용을 최소화시킬 수 있다.

 

Hilt에서 제공하는 표준 Component Hierarchy

 

Hilt에서 표준적으로 제공하는 Component 관련 Scope, 생성 및 파괴 시점

 

Hilt의 Annotation (Dagger 기반이기 때문에 상통하는 면이 있다.)

 

프로젝트에서 Hilt 사용하는 방법 (버전 카탈로그 사용시)

1. Version Catalog에 다음과 같이 hilt의 버전과 id를 작성해준다.

[versions]
kotlin = "2.0.0"
kapt = "2.0.0"
hilt = "2.51.1"

[libraries]
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
hilt-android-test = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" }

[plugins]
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
jetbrainKotlinKapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kapt" }
daggerHiltAndroid = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }

 

 

2. build.gradle.kts (Project: )수준에 추가한 hilt 플러그인을 입력해준다.

만약 빨간색으로 찾을 수 없다고 뜬다면 1번을 입력한 뒤 Sync now를 해준 뒤 시행해보자.

plugins {
    alias(libs.plugins.daggerHiltAndroid) apply false
    alias(libs.plugins.composeCompiler) apply false
    alias(libs.plugins.jetbrainKotlinKapt) apply false
}

 

 

3. build.gradle.kts (Module: app)수준에 추가한 hilt 플러그인, 라이브러리를 입력해준다.

이후, Sync now를 해준다.

plugins {
    alias(libs.plugins.daggerHiltAndroid)
    alias(libs.plugins.composeCompiler)
    alias(libs.plugins.jetbrainKotlinKapt)
}

...

dependencies {
    // Hilt
    implementation(libs.hilt.android)
    kapt(libs.hilt.compiler)

    // Hilt Device Test
    androidTestImplementation(libs.hilt.android.test)
    androidTestAnnotationProcessor(libs.hilt.compiler)

    // Hilt Local Unit Test
    testImplementation(libs.hilt.android.test)
    testAnnotationProcessor(libs.hilt.compiler)
}

kapt {
    correctErrorTypes = true
}

 

kapt - correctErrorTypes은 기본적으로 false값이다.

Hilt AnnotationProcessor는 선언된 클래스들의 시그니쳐를 정확히 구분해야한다. 기본적으로 kapt는 모르는 타입에 대해서는 Non-existent type으로 처리하게 된다. 이때 Hilt AnnotationProcessor가 이 클래스를 처리하지 못해 컴파일이 중단되는 경우가 있기 때문에 이 경우를 회피하기 위해 값을 true로 설정한다.

 

생성된 곳에서 오류가 발생한 타입을 추론하여 계속해서 진행할 수 있도록 만든다.

 

 

4. 이후 프로젝트에서 적용된 Hilt 라이브러리를 사용한다.

 

 

https://developer.android.com/training/dependency-injection/hilt-android?hl=ko#kts

 

Hilt를 사용한 종속 항목 삽입  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Hilt를 사용한 종속 항목 삽입 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Hilt는 프로젝트에서 종속

developer.android.com

 

Hilt를 사용해 애플리케이션에 적용하는 부분은 다음 포스트에서 다뤄보도록 하겠다.

 

 

++ 24-07-25 ++

오류는 다음 글을 참고하자. 요약하자면 글 수정전에는 kapt로 종속성 선언을 하지 않았기에 빌드 자체는 문제없었는데 실행시 바로 오류가 뽑혀져 나왔다. 이에 kapt 플러그인을 적용시키고 kapt로 hilt compiler를 종속성 선언하면 오류가 없어지고 정상적으로 빌드 및 실행이 된다는 내용이다.

https://small-stepping.tistory.com/1101

 

에러: 2 files found with path 'meta-inf/gradle/incremental.annotation.processors' from inputs:

Hilt 라이브러리를 적용하다 생긴 오류.  1번째 시도 : build.gradle.kts의 android {} 블록 내부에 META-INF 추가StackOverFlow에서 답변을 찾아본 결과 build.gradle.kts의 android {} 블록 내부에 있는 packaging {} 블

small-stepping.tistory.com