3 분 소요

Hilt

Hilt란 Google의 Dagger2를 기본으로 만든 의존성 주입 라이브러리이다. 따라서 Dagger2에 대한 Component, Scope를 기본적을 제공하며, 초기 프로젝트 환경 구축 비용을 크게 줄이는 것이 목적이 있다.

// Third Party Library라고 생각
class DataSource {
    fun getData() {
        Log.i("TAG", "데이터 다운로드..")
    }
}

Hilt에 대해 알아보기 전에 Dagger2를 사용하여 의존성 주입을 사용한 간단한 예시를 구성해 보면 다음과 같다.

@Module
class DataModule {

    @Provides
    fun providesDataSource(): DataSource {
        return DataSource()
    }
}

@Component(modules = [DataModule::class])
interface DataComponent {
    fun inject(mainActivity: MainActivity)
}

class App: Application() {
    lateinit var dataComponent: DataComponent
    
    override fun onCreate() {
        super.onCreate()
        dataComponent = DaggerDataComponent.builder()
            .build()
    }
}

class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var dataSource: DataSource

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        (application as App).dataComponent
            .inject(this)

        // 사용하기
        dataSource.getData()
    }
}
<application
    android:name=".App"
    .
    .
</application>

Dagger2로 적용되어있는 간단한 예시를 Hilt를 알아보면서 바꿔보도록 한다.

종속 항목 추가

먼저 hilt-android-gradle-plugin 플러그인을 프로젝트의 루트 build.gradle 파일에 추가한다.

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
    }
}

app/build.gradle 파일에 종속 항복을 추가한다.

plugins {
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

dependencies {
    // Dagger - Hilt
    implementation "com.google.dagger:hilt-android:2.41"
    kapt "com.google.dagger:hilt-android-compiler:2.41"
}

Hilt는 자바8 기능을 사용한다. 대부분 자바8을 사용하기 때문에 이미 되어있는 부분일 수 있다.

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

version 범블비

안드로이드 스튜디오 범블비 버전에선 build.gradlesettings.gradle이 변화하였다.

build.gradle에 있던 이 부분이 settings.gradle로 이동하였다.

repositories {
    gradlePluginPortal()
    google()
    mavenCentral()
}

build.gradle 의 classpath 설정하던 부분이 다음과 같이 변경되었다.

plugins {
    id 'com.android.application' version '7.1.1' apply false
    id 'com.android.library' version '7.1.1' apply false
    id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}

Hilt의 2.41버전이 나오면서 classpath는 build.gradle에 다음과 같이 넣어주면 된다.

plugins {
    id 'com.android.application' version '7.1.1' apply false
    id 'com.android.library' version '7.1.1' apply false
    id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
    id 'com.google.dagger.hilt.android' version '2.41' apply false
}

@HiltAndroidApp

class App: Application() {
    lateinit var dataComponent: DataComponent
    
    override fun onCreate() {
        super.onCreate()
        dataComponent = DaggerDataComponent.builder()
            .build()
    }
}

Hilt에서 @HiltAndroidApp 어노테이션을 사용하면 컴파일 타임시 표준 Component 생성에 필요한 클래스들을 초기화한다. 따라서 Hilt를 프로젝트에 적용하기 위해서는 필수적으로 요구되는 과정이다.

Dagger를 적용하였는 App 클래스를 Hilt로 변경하면 다음과 같이 코드가 줄어든다.

@HiltAndroidApp
class App: Application()
<application
    android:name=".App"
    .
    .
</application>

Component hierachy

기존의 Dagger2는 개발자가 직접 필요한 Component들을 작성하고 상속 관계를 정의하여 사용하였지만, Hilt에서는 Android 화경에서 표준적으로 사용되는 Component들을 기본적으로 제공하고 있다.

또한 Hilt 내부적으로 제공하는 Component들의 전반적인 생명주기 또한 자동으로 관리를 해주기 때문에 초기 DI 적용을 위한 환경을 구축하는데 드는 비용을 줄일수 있다.

위와 같은 Hilt에서 제공하는 Component들은 아래와 같은 함수 호출시점에 생성되고, Destroy가 된다.

생성된 구성요소 생성 위치 제거 위치
SingletonComponent Application#onCreate() Application#onDestroy()
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy()
ActivityComponent Activity#onCreate() Activity#onDestroy()
FragmentComponent Fragment#onAttach() Fragment#onDestroy()
ViewComponent View#super() 제거된 뷰
ViewWithFragmentComponent View#super() 제거된 뷰
ServiceComponent Service#onCreate() Service#onDestroy()

각 컴포넌트들은 생성 시점부터 제거되기까지 Member Injection이 가능하고, 각 컴포넌트마다 아래와 같은 생명주기를 갖는다.

  • SingletonComponent: Application 전체의 생명주기를 가진다.
  • ActivityRetainComponent: ApplicationComponent의 하위 컴포넌트로써, Activity의 생명주기를 가진다.
    • Activity의 화면 회전과 같은 작업이 일어났을시 파괴되지 않고 유지된다.
  • ActivityComponent: ActivityRetainComponent의 하위 컴포넌트로써, Activity의 생명주기를 가진다.
  • FragmentComponent: ActivityComponent의 하위 컴포넌트로써, Fragment가 Activity에 붙는(onAttach()) 시점에 생성이 되고, Fragment가 파괴되는 시점에 제더된다.
  • ViewComponent: ActivityComponent의 하위 컴포넌트로써, View의 생명주기를 가진다.
  • ViewWithFragmentComponent: FragmentComponent의 하위 컴포넌트로써, Fragment의 view 생명주기를 가진다.
  • ServiceComponent: ApplicationComponent의 하위 컴포넌트로써, Service의 생명주기를 가진다.

위와 같은 표준 Component/Scope들은 Hilt에서는 제공하고 있으며, 새로운 Component를 정의하고 싶다면 @DefineComponent 어노테이션을 사용하여 정의가 가능하다.

@InstallIn

Dagger2에서는 새로운 module을 생성하면, 사용자가 정의한 Component에 module 클래스를 직접 넣어주는 방법을 사용하였다.

@Component(modules = [DataModule::class])
interface DataComponent {
    fun inject(mainActivity: MainActivity)
}

반면에 Hilt에서는 표준적으로 제공하는 Component들이 존재하기 때문에 @InstallIn 어노테이션을 사용하여 표준 Component에 Module을 install 할 수 있다.

Dagger에서 사용하였던 DataComponent를 삭제하고 DataModule에 @InstallIn을 추가한다.

@Module
@InstallIn(SingletonComponent::class)
class DataModule {
    @Provides
    fun providesDataSource(): DataSource {
        return DataSource()
    }
}

@AndroidEntryPoint

class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var dataSource: DataSource

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        (application as App).dataComponent
            .inject(this)

        // 사용하기
        dataSource.getData()
    }
}

Dagger2에서는 의존성을 주입받을 대상을 전부 dependency graph에 지정을 해주었지만, Hilt에서는 객체를 주입할 대상에게 @AndroidEntryPoint 어노테이션을 추가만 해주면 된다. @AndroidEntryPoint를 추가할 수 있는 Android Component는 아래와 같다.

  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var dataSource: DataSource

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 사용하기
        dataSource.getData()
    }
}


References

맨 위로 이동 ↑

댓글남기기