안드 공부를 해볼까?

[Android] Hilt란 무엇일까 본문

안드로이드/개념 정리

[Android] Hilt란 무엇일까

문바리 2023. 2. 21. 22:36
728x90

1. 개요

프로젝트를 할때마다 편하게 쓰는 Hilt를 더 배우고 싶어 DI부터 다시 정리할려고 합니다.

본 포스트는 드로이드나이츠 2020, 옥수환 님의 Hilt 강의를 보며 작성하였습니다.

2. 본문

2-1. DI란 무엇일까?

DI(Dependency Injection)은 의존성 주입을 뜻합니다.

/** DI가 없는 코드 */
class MemoRepository{
	private val db = SQLiteDatabase()
    
	fun load(id : String){...}
}



/** DI를 한 코드 */
class MemoRepository(private val db : SQLiteDatabase){
	fun load(id : String) {...}
 }

2가지 코드 모두 db를 사용할 수 있습니다. 그렇다면 차이점은 무엇일까요?

DI가 없는 코드는 db 생성에 대한 책임을 MemoRepository가 갖지만 DI를 한다면 책임을 가지지 않습니다.

외부에서 db를 생성하고 생성자로 주입을 받기 때문입니다.

2-2. Android DI Framework

안드로이드에서도 DI를 위해 다양한 Framework를 제공합니다.

그 중 Dagger2를 살펴보겠습니다.

 

장점

- 컴파일 시점에 의존성 주입에 대한 코드를 생성

- 명확한 코드, 디버깅이 가능함

- 자원 공유의 단순화

- 플레이 스토어 상위 10000개의 안드로이드 앱 중 74%가 사용중

 

단점

- 배우기 어렵고, 프로젝트 설정이 힘들다.

- 간단한 프로그램에는 오히려 더 번거롭다.

- 같은 결과에 대해 다양한 방법이 존재한다.

 

배우기도 힘들고 간단한 프로그램에서는 더 힘든데 왜 써야할까요?

바로 중요한 것을에 더 집중할 수 있게 하기 위함입니다.

개발을 하는데 컴파일 시점에서 오류를 잡아주고 다른 것에 더 집중하도록 도와주기 때문입니다.

또한, 한번 설정해두면 쉽게 활용할 수 있어 개발 속도에도 도움을 줍니다.

2-3. Hilt

러닝커브가 높고 한가지 결과에 다양한 방법이 존재했던 Dagger2를 표준적인 방법으로 바꾼 DI Framework입니다.

Dagger보다 표준화된 컴포넌트 세트, 스코프 설정으로 가독성/이해도를 쉽게 했고 다양한 빌드 타입에 대해 다른 바인딩을 제공합니다.

// app 모듈의 build.gradle

plugins {
    id 'kotlin-kapt'
}

dependencies {
    implementation libs.hilt
    // KAPT(Kotlin Annotation Processing Tools), 어노테이션 사용, annotationProcessor의 대체
    kapt libs.hilt.compiler
}
// 프로젝트 단위의 build.gradle
plugins {
    alias libs.plugins.hilt apply false
}

## version catalog, libs.version.toml
[versions]
hilt = "2.44"

[libraries]
# 의존성 주입 라이브러리, Hilt
hilt = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt_compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" }

[plugins]
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }

(예제는 Version Catalog를 적용했습니다.)

2-3-1. Hilt의 특징

Hilt는 Dagger2 라이브러리를 기반으로 개발되었습니다.

Dagger2보다 표준화된 사용법을 제시하고 보일러 플레이트 코드를 감소시킵니다.

실제로 구글 샘플 앱을 Dagger에서 Hilt로 바꾼 결과, 코드가 반으로 줄었다고 합니다.

또한, 쉬운 모듈 탐색과 통합, 개선된 테스트 환경, 프로젝트 설정 간소화 등 다양한 장점이 존재합니다.

2-3-2. Hilt 간단하게 적용하기

Hilt의 의존성 주입은 바로 @Inject를 통해 주입이 가능합니다.

class MemoRepository @Inject constructor (private val db : SQLiteDatabase){
     fun load(id : String) {...}
 }

또한, 사용하기 위해서는 Application에서 @HiltAndroidApp을 설정을 해줘야 합니다.

의존성 주입은 onCreate의 super.onCreate()에서 바이트 코드로 변환 뒤 이루어집니다.

(코드를 보면 Hilt_MemoApplication으로 생성됐을 겁니다.)

@HiltAndroidApp
class MemoApp : Application()

이제 Activity에서 사용할 수 있습니다. @AndroidEntryPoint라는 어노테이션을 붙여 사용합니다.

(만약 ContentProvider, DFM, Dagger를 사용하지 않는 3rd-party 라이브러리 등은 @EntryPoint를 사용합니다.)

이 외에 Activity, Fragment, View, Service, BroadcastReceiver도 사용 가능합니다.

또한 생성자 외에도 변수에 @Inject를 할 수 있습니다. 단, lateinit으로 설정해야 합니다(private도 금지)

@AndroidEntryPoint
class MemoActivity : AppcompatActivity{
    @Inject lateinit var repository : MemoRepository
    
    override fun onCreate(savedInstanceState : Bundle){
    	super.onCreate(bundle)
        repository.load("...")
    }
}

@EntryPoint
@InstallIn(SingletonComponent::class)
interface FooBarInterface{
    fun getBar() : Bar
}


// EntryPoint 활용
val bar = EntryPionts.get(applicationContext, FooBarInterface::class.java).getBar()

이제 의존성 주입을 위한 모듈을 만들어야 합니다.

@InstallIn(SingletonComponent::class)
@Module
object DataModule{
    @Provides
    fun provideMemoDB(@ApplicationContext context : Context) = 
    	Room.databaseBuilder(context, MemoDatabase::class.java, "Memo.db").build()
}

@InstallIn: Hilt가 생서하는 DI 컨테이너에 어떤 모듈을 사용할지 가르킨다.

@Module: InstallIn으로 설정한 객체들의 모음입니다.

@Provides: 의존성 주입을 합니다.

@ApplicationContext: applicationContext를 제공합니다.

 

간단한 그림으로 살펴보겠습니다.

출처: 드로이드나이츠 2020, 옥수환 - Hilt

1. @HiltAndroidApp을 통해 ApplicationComponent가 생성됩니다.

2. @InstallIn을 통해 해당 컴포넌트의 모듈이 생성됩니다.

3. MemoActivity에 @AndroidEntryPoint를 통해 ActivityComponent가 생성됩니다.

4. 이제 MemoRepository를 주입받을 수 있습니다.

2-3-3. Hilt의 다양한 컴포넌트

Hilt는 이미 정의해둔 컴포넌트를 사용할 수 있습니다.

하위 컴포넌트는 직계 컴포턴트의 상위 컴포넌트가 가지고 있는 의존성에 가질 수 있습니다.

만약 싱글톤으로 한개의 객체로 여러 곳에서 사용할때 예제를 보겠습니다.

DataModule에 SingletonComponent로 생성했으니 @Singleton으로 설정해야합니다.

모듈에 설정한 컴포넌트에 맞춰서 꼭 어노테이션을 설정해야합니다.

@Singleton
class MemoRepository @Inject constructor (private val db : SQLiteDatabase){
     fun load(id : String) {...}
 }
@InstallIn(SingletonComponent::class)
@Module
object DataModule{
    @Provides
    fun provideMemoDB(@ApplicationContext context : Context) = 
    	Room.databaseBuilder(context, MemoDatabase::class.java, "Memo.db").build()
}

또한, Hilt는 @HiltViewModel을 통해 ViewModel도 간단하게 구현 가능합니다.

@HiltViewModel
class MemoViewModel @Inject constructor(private val memoRepo : MemoRepository) : ViewModel(){
  ...
}

3. 결론

매일 프로젝트를 진행하며 Hilt를 SingletonComponent로만 생성했고 그에 대한 장점은 몰랐습니다.

"보일러 플레이트 코드 감소", "리소스 관리 용이" 등 다양한 장점이 있었고 사용법도 있었습니다.

다만 2020년 자료를 활용하다 보니 아쉬운 점도 많았고 다음 포스팅때는 더 보충해서 작성하겠습니다.

반응형
Comments