[Android / Kotlin] Hilt 의존성 작성하기

Subeen·2024년 2월 20일
1

Android

목록 보기
64/73

DI (Dependency Injection)란?

DI는 의존성 주입이다. 프로그래밍에서 의존성이라 함은 함수에 필요한 클래스 또는 참조 변수나 객체에 의존하는 것이라고 할 수 있다.
➡️ A 클래스가 객체를 만들기 위해 B를 필요로 할 때 B는 A의 의존 대상이 되는 것이다.
🫨 자동차를 만드는 회사는 타이어를 직접 생산하지 않으니 타이어 회사에 의존하게 되는 것이고, 타이어 회사가 자동차 회사의 의존 대상이 된다.

/*
 * 의존성을 주입하지 않은 코드
 * Laptop 클래스는 Cpu 클래스에 의존하고 있다.
 * 의존성은 private val cpu = Cpu() 부분에서 생성된 인스턴스를 직접 만들어 사용하는 것을 의미한다.
 */
class Laptop {
    private val cpu = Cpu()
    fun start() {
        cpu.start()
    }
}
fun main(args: Array) {
    val laptop = Laptop()
    laptop.start()
}
/*
 * 의존성을 주입한 코드
 * Laptop은 외부에서 Cpu 객체를 받아 사용하게 되므로 의존성 주입이 이루어진 것이다.
 * 결합이 낮아지고 Laptop을 재사용하기가 쉬워진다.
 */
class Laptop(private val cpu: Cpu) {
    fun start() {
        cpu.start()
    }
}
fun main(args: Array) {
    val cpu = Cpu()
    val laptop = Laptop(cpu)
    laptop.start()
}

DI의 장단점

장점

  • Unit Test가 용이하다.
  • 코드의 재사용성이 높아진다.
  • 리팩토링이 수월하다.
  • 객체 간의 의존성을 줄이거나 없앨 수 있다.
  • 객체 간의 결합도를 낮추며 유연한 코드를 짤 수 있다.

단점

  • 의존성 주입을 위한 선행 작업이 필요하다.
  • 코드를 추적하고 읽기가 어려워진다.

Hilt

Hilt는 구글의 Dagger를 기반으로 만든 DI 라이브러리이다. Hilt는 기존에 많이 사용 되면 Koin에 비해 어려우며 컴파일 시에 stub 파일을 생성하고 그래프를 구성하기 때문에 빌드 시간이 상대적으로 느리지만 AndroidX 라이브러리와 호환되며 Android Component별 Scope가 명확하다.

dependency

project 레벨의 build.gradle에 dependency를 추가한다.

plugins {
    ...
    id("com.google.dagger.hilt.android") version "2.41" apply false
}

app 레벨의 build.gradle에 dependency를 추가한다.

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

dependencies {
	...

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

Application Class

Hilt Component의 가장 상위 스코프가 되는 Application Class를 작성하고 이 클래스를 Manifest 등록해준다.

@HiltAndroidApp
class ImageSearchApplication : Application() {

}
  • AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:name="ImageSearchApplication"
        ...
        >
        <activity
            android:name=".ui.main.MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

프로젝트를 빌드 하면 Hilt에 의해 자동으로 의존성 그래프 파일이 생성되는 것을 확인할 수 있다.

의존성을 Hilt로 대체

@Singleton과 @Provides가 무엇을 의미할까?
➡️ @Singleton과 @Provides는 Dagger 의존성 주입 라이브러리에서 사용되는 주석이다.
☝🏻 @Singleton은 해당 객체가 애플리케이션 전체에서 하나의 인스턴스만 생성되도록 보장한다. 즉, 해당 객체가 싱글톤으로 사용되도록 지정하며 메모리를 최적화하고 객체의 일관된 상태를 유지하는데 도움이 된다 !
✌🏻 @Provides는 의존성을 제공하는 메서드를 나타낸다. 즉, 해당 메서드는 어떤 종류의 객체든지 생성하고 제공할 수 있는 메서드이다. Dagger는 @Provides 메서드를 통해 의존성 객체를 생성하고 관리한다 !


@Module // 해당 클래스가 Dagger Hilt 모듈임을 나타냄
@InstallIn(SingletonComponent::class) // 해당 모듈이 애플리케이션 전체에서 사용되는 싱글톤 컴포넌트에 설치되어야 한다는 것을 나타냄 
class AppModuleClass {

    // 로깅에 사용할 OkHttp 클라이언트를 주입하는 ProvideOkHttpClient
    @Singleton
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        val httpLoggingInterceptor = HttpLoggingInterceptor()
            .setLevel(HttpLoggingInterceptor.Level.BODY)
        return OkHttpClient.Builder()
            .addInterceptor(httpLoggingInterceptor)
            .build()
    }

    // Retrofit 객체를 작성하는 ProvideRetrofit
    @Singleton
    @Provides
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .addConverterFactory(GsonConverterFactory.create())
            .client(okHttpClient)
            .baseUrl(Constants.BASE_URL)
            .build()
    }

    //KaKaoSearchApi Service 객체를 작성하는 KaKaoSearchApiService
    @Singleton
    @Provides
    fun provideApiService(retrofit: Retrofit): KaKaoSearchApi {
        return retrofit.create(KaKaoSearchApi::class.java)
    }
    
    // provideSharedPreferences 메서드에서 생성되는 SharedPreferences 객체가 애플리케이션 전체에서 단일 인스턴스로 유지되어야 함
    @Singleton
    @Provides
    fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
        return context.getSharedPreferences(Constants.PREFERENCE_NAME, 0)
    } 
}

RepositoryModule

인터페이스인 Repository를 주입하기 위한 RepositoryModule을 작성해준다.
ImageSearchRepository의 경우 인터페이스이기 때문에 Binds를 사용해서 Hilt가 의존성 객체를 생성할 수 있도록 설정해준다.
➡️ 정리하면, RepositoryModule은 어떤 클래스가 ImageSearchRepository 인터페이스를 구현하도록 바인딩 하는 역할을 한다.

@Module
@InstallIn(SingletonComponent::class)
class RepositoryModule {

    @Singleton
    @Binds
    abstract fun bindImageSearchRepository(
        imageSearchRepositoryImpl: ImageSearchRepositoryImpl
    ) : ImageSearchRepository
}

참조
DI와 Hilt

profile
개발 공부 기록 🌱

0개의 댓글