의존성 주입
은 의존 관계에 있는 클래스의 객체를 외부로 부터 생성하여 주입받는 것입니다.
의존성 주입을 적용한 코드와 적용하지 않은 코드를 비교해 보면 더 잘 이해할 수 있습니다.
// 의존성 주입이 없는 코드
class MemoRepository {
private val db = SQLiteDatabase()
fun load (id: String) {...}
}
fun main() {
val repository = MemoRepository()
repository.load("8092")
}
// 의존성 주입을 적용한 코드
class MemoRepository(private val db: Database) {
fun load (id: String) {...}
}
fun main() {
val db = SQLiteDatabase()
val repository = MemoRepository(db)
repository.load("8092")
}
Dagger Hilt는 2020년 6월 Google에서 오피셜하게 발표한 Android 전용 DI 라이브러리입니다. Hilt는 Dagger2를 기반으로 Android Framework에서 표준적으로 사용되는 DI component와 scope를 기본적으로 제공하여, 초기 DI 환경 구축 비용을 크게 절감시키는 것이 가장 큰 목적입니다.
Hilt 를 사용하는 모든 앱은 @HiltAndroidApp 어노테이션을 포함하는 application 을 생성해야 합니다. @HiltAndroidApp은 Hilt 컴포넌트의 코드 생성과 컴포넌트를 사용하는 Application의 기본 클래스를 생성하게 됩니다.
@HiltAndroidApp
class App : Application () {
override fun onCreate() {
super.onCreate()
}
}
Application에서 멤버 주입이 가능하게 설정하고 나면, 다른 안드로이드 클래스들에서도
@AndroidEntryPoint 어노테이션을 사용하여 안드로이드에 DI 컨테이너를 추가하여 멤버 주입을 하는 것이 가능해 집니다.
@AndroidEntryPoint를 사용할 수 있는 타입은 다음과 같습니다.
@AndroidEntryPoint
class MyActivity : MyBaseActivity() {
@Inject lateinit var bar: Bar // ApplicationComponent 또는 ActivityComponent으로 부터 의존성이 주입된다.
override fun onCreate() {
// super.onCreate()에서 의존성 주입이 발생한다.
super.onCreate()
// Do something with bar ...
}
}
때로 생성자 삽입할 수 없는 상황도 있습니다. 예를 들어 인터페이스를 생성자 삽입할 수 없습니다. 또한 외부 라이브러리의 클래스와 같이 소유하지 않은 유형도 생성자 삽입할 수 없습니다. 이럴 때는 Hilt 모듈을 사용하여 Hilt에 결합 정보를 제공할 수 있습니다.
모듈은 컴포넌트에 의존성을 제공하는 역할을 합니다. 클래스에 @Module 어노테이션을 붙여 모듈 클래스를 만들 수 있습니다. 모듈 클래스 내에 선언되는 매서드에 @Provides 어노테이션을 붙여 컴파일 타임에 의존성을 제공하는 바인드된 프로바이더를 생성합니다.
간단히 설명하면 @Module 은 의존성을 제공하는 클래스에 붙이고, @Provides 는 의존성을 제공하는 메소드에 붙인다고 생각하면 됩니다.
Hilt의 모듈은 @InstallIn이라는 추가적인 어노테이션을 갖습니다. @InstallIn은 Hilt의 표준 컴포넌트들 중 어떤 컴포넌트에 모듈을 설치할지 결정합니다.
@Module
@InstallIn(ApplicationComponent.class) // 생성되는 ApplicationComponent에 FooModule을 설치함
public final class FooModule {
@Provides
static Bar provideBar() {...}
}
인터페이스라면 이 인터페이스를 생성자 삽입할 수 없습니다. 대신 Hilt 모듈 내에 @Binds로 어노테이션이 지정된 추상 함수를 생성하여 Hilt에 결합 정보를 제공합니다.
@Binds 어노테이션은 인터페이스의 인스턴스를 제공해야 할 때 사용할 구현을 Hilt에 알려줍니다.
인터페이스가 유형을 생성자 삽입할 수 없는 유일한 경우는 아닙니다. 클래스가 외부 라이브러리에서 제공되므로 클래스를 소유하지 않은 경우(Retrofit, OkHttpClient 또는 Room 데이터베이스와 같은 클래스) 또는 빌더 패턴으로 인스턴스를 생성해야 하는 경우에도 생성자 삽입이 불가능합니다.
클래스를 직접 소유하지 않으면 Hilt 모듈 내에 함수를 생성하고 이 함수에 @Provides 어노테이션을 지정하여 이 유형의 인스턴스를 제공하는 방법을 Hilt에 알릴 수 있습니다.
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {
@Provides
fun provideAnalyticsService(
// Potential dependencies of this type
): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(AnalyticsService::class.java)
}
}
기존의 Dagger와 다르게 Hilt사용자는 Dagger 컴포넌트를 직접적으로 정의하거나 인스턴스화 할 필요가 없어졌습니다. Hilt는 안드로이드 Application의 다양한 생명주기에 자동으로 통합되는 내장 컴포넌트 세트를 해당 스코프 어노테이션과 함께 제공합니다. 아래에 있는 다이어그램은 표준 Hilt 컴포넌트 계층을 보여주고 있습니다다. 각 컴포넌트에 위에달린 어노테이션은 컴포넌트 바인딩의 생명주기를 지정하는 데 사용됩니다. 각 컴포넌트 아래에 있는 화살표는 하위 컴포넌트를 가르키고 있습니다. 하위 컴포넌트의 바인딩은 상위 컴포넌트의 바인딩이 가지고 있는 의존성들을 가질 수 있습니다.
@InstallIn이 달린 모듈의 바인딩에 스코프가 지정될 때는 반드시 모듈이 설치되는 컴포넌트의 스코프와 일치해야 합니다. 예를 들면, @InstallIn(ActivityComponent.class) 모듈은 @ActivityScoped 만 사용할 수 있습니다.
컴포넌트의 수명은 안드로이드 클래스에 대응하는 인스턴스 생성과 소멸을 따라갑니다. 컴포넌트가 생성되고 종료될 때, 해당 스코프 어노테이션이 지정된 바인딩 또한 수명을 함께합니다. 컴포넌트 수명은 멤버 주입된 값들이 사용될 수 있는 시기를 나타냅니다.
다음 표는 스코프 어노테이션과 각 컴포넌트에 맞는 수명을 목록으로 보여주고 있습니다.
@InstallIn(ApplicationComponent::class)
@Module
class RetrofitCreate {
@Provides
@Singleton
fun createRetrofit(): Retrofit {...}
}
}
Hilt는 jetpack 라이브러리 클래스를 위해 extensions 를 제공합니다. 지원하는 컴포넌트로 ViewModel, WorkManager 가 있습니다.
class MainViewModel @ViewModelInject constructor(
private val loginRepositoryImpl: LoginRepositoryImpl
) : BaseViewModel() {...}
[Android Developers] Hilt를 사용한 종속 항목 삽입
[드로이드나이츠 2020] 옥수환 - Hilt와 함께 하는 안드로이드 의존성 주입
[하이퍼커넥트 기술블로그] Dagger Hilt로 안드로이드 의존성 주입 시작하기