안드로이드 Hilt 라이브러리 사용법

MSU·2024년 7월 31일

Android

목록 보기
11/36
post-thumbnail

앱 단위, 프로젝트 단위의 build.gradle.kts 파일에 아래와 같이 추가해준다.

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.jetbrains.kotlin.android) apply false
    id("com.google.dagger.hilt.android") version "2.44" apply false
}

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.jetbrains.kotlin.android)

    // hilt
    id("kotlin-kapt")
    id("com.google.dagger.hilt.android")
}


...


dependencies {

    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
    implementation(libs.material)
    implementation(libs.androidx.activity)
    implementation(libs.androidx.constraintlayout)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)

    // hilt
    implementation("com.google.dagger:hilt-android:2.44")
    kapt("com.google.dagger:hilt-android-compiler:2.44")
}

// Allow references to generated code
kapt {
    correctErrorTypes = true
}

build.gradle파일 수정후 항상 sync now로 동기화해준다.

상단 폴더에 App 클래스 파일을 만들고 Application 클래스를 상속받는다
클래스 위에 @HiltAndroidApp 어노테이션을 추가한다.

@HiltAndroidApp
class App: Application() {

}

AndroidManifest.xml에 추가한 App을 등록해준다

이 상태로 빌드를 하면 아래와 같이 파일이 생성되는 것을 확인할 수 있다.

이제 실제로 의존성 주입을 사용해보기 위해 임의의 클래스를 작성한다.

class MyName {

    override fun toString(): String {
        return "테스트 리턴"
    }
}

App에서 해당 클래스의 객체를 주입받고 출력해준다.

@HiltAndroidApp
class App: Application() {

    val TAG: String = App::class.java.simpleName

    // App이 클라이언트로써 싱글톤 컴포넌트에게 MyName를 원한다고 요청을 한다
    @Inject
    lateinit var myName: MyName

    override fun onCreate() {
        super.onCreate()

        Log.d(TAG,"My name is $myName")
    }
}

이 상태로 빌드를 실행하면 에러가 발생한다.
아직 바인딩이 안되어있는 상태이기 때문에 바인딩 처리를 해줘야 한다.

생성자 바인딩을 통해 해결할 수 있다.

생성자 바인딩 방법

class MyName @Inject constructor() {

    override fun toString(): String {
        return "테스트 리턴"
    }
}

다시 빌드를 실행하면 아래와 같이 정상적으로 출력되는 것을 확인할 수 있다.

이 때 onCreate코드 안에서 super.onCreate() 호출 이전에 로그 출력하는 코드를 작성하게 된다면

    override fun onCreate() {
        Log.d(TAG,"My name is $myName")
        super.onCreate()
    }

다시 빌드를 실행 시 에러가 발생하는 것을 확인할 수 있다.

왜냐하면 super.onCreate() 호출 이후에 의존성 주입이 실행되기 때문이다.
따라서 super.onCreate() 호출 이후에 의존성 주입이 되도록 주의해야 한다.

생성자 바인딩 외에 모듈을 통한 바인딩 방법이 있다.

모듈을 이용한 바인딩

object로 싱글톤 클래스를 작성 후 @Module, @InstallIn 어노테이션을 붙여줘야 한다.

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
}

그리고 MyName객체를 반환하는 함수를 작성해주고 @Provides라는 어노테이션을 붙여준다.
이전에 작성한 MyName 클래스의 생성자 바인딩 부분은 지워준다.

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    fun provideMyName(): MyName{
        return MyName()
    }
}

class MyName {

    override fun toString(): String {
        return "테스트 리턴"
    }
}

다시 빌드를 실행해주면 아래와 같이 출력이 잘 되는 것을 확인할 수 있다.

모듈을 통한 바인딩을 방법도 아래와 같이 파일들이 생성되는 것을 확인할 수 있다.

실제로 아래와 같은 형태로 App 인스턴스의 myName 프로퍼티에 주어진 MyName 객체가 넘겨지는 것을 확인할 수 있다.

MainAcitivity에서도 아래와 같이 코드를 작성할 수 있다.

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    lateinit var binding: ActivityMainBinding

    @Inject
    lateinit var myName: MyName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        
        binding.textView.text = "${myName}"
    }
}

이 때 클래스 위에 @AndroidEntryPoint 어노테이션을 붙여줘야 하면 붙여주지 않는다면 아래와 같이 lateinit 변수가 초기화 되어있지 않다는 에러를 확인할 수 있다.

Hilt 주요 Annotation 종류

@HiltAndroidApp

@AndroidEntryPoint

@Module

@InstallIn

@HiltViewModel

Annotation은 자바 5버전부터 추가되었다.
자바(코틀린) 소스코드에 추가하는 메타데이터로 컴파일러에게 부가 정보를 제공한다.
클래스, 필드, 메서드 및 기타 요소에 선택적으로 선언 가능하다
런타임에서도 참조가 가능하다

Annotation Processor : 컴파일 타임에 Annotation을 스캔하고 소스코드를 검사 또는 생성한다.

바이트 코드 변조

Dagger와 달리 Hilt에서 새롭게 바이트 코드 변조라는 기능이 추가되었다.

바이트 코드란 자바 소스코드가 컴파일을 거쳐 나온 결과물을 말한다.

Transform API
AGP(Android Gradle Plugin)에 포함된 api이다.
중간 빌드 산출물들을 처리한다
바이트 코드 변환을 위한 gradle task를 생성한다.
AGP는 변조된 내용들 사이의 의존성을 핸들링한다.

안드로이드 앱 빌드 과정
소스 코드 -> 컴파일러 -> 바이트 코드 -> D8 -> Dex -> APK/AAB
여기서 바이트 코드와 D8 단계 사이에 바이트 코드 변조가 들어간다.
소스 코드 -> 컴파일러 -> 바이트 코드 -> 바이트 코드 변조 -> D8 -> Dex -> APK/AAB

Hilt의 전체 프로세스

Hilt annotation이 포함된 소스 코드 -> annotation 프로세싱 -> 생성된 소스코드 -> 컴파일 -> 바이트 코드 산출 -> 바이트 코드 변조 -> D8 컴파일 이후 APK/AAB 패키징

profile
안드로이드공부

0개의 댓글