Hilt는 온라인 슬롯 주입 라이브러리이다. 그래서 Hilt에 대해서 알고 싶다면 먼저 온라인 슬롯 주입이라는 개념을 알아야 한다.
온라인 슬롯 주입
의존성 주입(Dependency Injection, DI)은 객체가 필요로 하는 의존성을 외부에서 제공하는 디자인 패턴이다. 이를 통해 온라인 슬롯 간의 결합도를 낮추고, 유닛 테스트를 용이하게 하며, 코드의 재사용성과 확장성을 높일 수 있다.
하나의 온라인 슬롯가 다른 온라인 슬롯를 참조할 필요가 있을 때 의존성이 생긴다. 예를 들어 A온라인 슬롯에서 B온라인 슬롯의 인스턴스를 필요로 한다면 A온라인 슬롯는 B온라인 슬롯에 의존성이 생기는 것이다. 이때 A온라인 슬롯는 B온라인 슬롯에 의존한다고 한다. 이러한 의존성은 여러 가지 방법으로 제공할 수가 있다. 가장 간단한 방법으로는 A온라인 슬롯 내에서 B온라인 슬롯의 인스턴스를 생성하는 것이다. 코드로 나타내면 다음과 같다.
class A { private val b = B() }
이런 구조에서 문제점은 무엇일까? A온라인 슬롯를 UserService 온라인 슬롯로 바꾸고 B온라인 슬롯를 UserRepository 온라인 슬롯로 바꿔보자.
이때 UserRepository는 서버 1에서만 User 데이터를 가져온다고 가정하자. 그러면 UserService 객체는 서버 1에서만 데이터를 가져오는 책임을 가진다. 만약 서버 2에서만 User 데이터를 가져오는 책임을 가지는UserService객체를 만들려면 어떻게 해야 할까? UserService 온라인 슬롯가서버 2에서만 User 데이터를 가져오는 새로운 온라인 슬롯에 의존하는 것이다. 새로운 온라인 슬롯를 UserRepository2라고 하자. 그러면 다음과 같이 된다.
이번에는 무슨 문제가 있는가? UserService 온라인 슬롯는 서버 1에서만 User 데이터를 가져오는 기능을 잃었다. 결국 서버 2에서만 User 데이터를 가져오는 새로운 온라인 슬롯를 만들어야 한다. 그 온라인 슬롯를 UserService2라고 하면 다음과 같이 된다.
최종적으로 서버 1과 서버 2에서 각각 데이터를 가져오는UserService 온라인 슬롯와UserService2 온라인 슬롯가 만들어진다. 하지만 두 온라인 슬롯를 자세히 보면 중복코드가 있다. 재사용하고 싶은 욕망이 들끓지 않는가? 이때 의존성을 주입해 주면 된다.용어 자체는 거창하지만 사실 말 그대로 온라인 슬롯 주입, 즉 여기서는UserService 온라인 슬롯에UserRepository 온라인 슬롯를 주입하면 되는 것이다. 방법은 간단하게UserService 온라인 슬롯생성자에UserRepository 인스턴스를 전달하면 된다. (생성자 말고 필드로 주입하는 방법도 있다.)
그런데 문제가 있다. 생성자로 넘겨주는 데이터 타입이UserRepository이기 때문에UserRepository2 인스턴스는 넘겨줄 수가 없다. 이럴 때추상화를 사용하면 된다.UserRepository를 인터페이스로 만들고 해당 인터페이스의 구현체로 UserRepository1Impl과 UserRepository2Impl를 만들어서 각각 서버 1과 서버 2에 접속하도록 하면 된다. 예시 코드는 실제 서버에 접속할 수 없으니 각각의 서버에서 데이터를 가져온 척하는 문자열을 반환하도록 하였다.
이렇게 하면UserService 온라인 슬롯를 그대로 사용하면서 필요한 의존성을 주입받을 수 있다. 서버 1에 접속하든 서버 2에 접속하든UserService 온라인 슬롯에는 별도의 추가 또는 수정 사항이 발생하지 않는다. 요란했던 의존성 주입의 끝이다. 이렇게 직접 의존성 주입을 하는 것을 수동 DI라고 한다.
Hilt란?
Hilt는 안드로이드 앱의 온라인 슬롯 주입을 간소화해주는 라이브러리이다. 온라인 슬롯 주입 라이브러리인 Dagger를 기반으로 만들어졌다. Dagger를 기반으로 안드로이드에서 더 쉽게 사용할 수 있도록 만든 라이브러리가 Hilt라고 보면 된다. Hilt는 안드로이드 컴포넌트의 생명주기를 관리한다. Hilt와 Dagger 모두 컴파일 타임에 온라인 슬롯 주입을 하기 때문에 런타임에 발생할 오류를 최소화한다.
기본 사용 방법
앞선 예제를 조금 수정한 새로운 예제를 기반으로 한다. ViewModelFactory를 사용하지 않고 간단히 ViewModel에 온라인 슬롯 주입을 하는 내용을 포함한다.
1) 플러그인 추가
프로젝트 수준의 build.gradle에 아래 플러그인을 추가한다.
id("com.google.dagger.hilt.android") version "2.48.1" apply false
MyApplication 온라인 슬롯를 만들고 Application을 상속받는다.해당 애플리케이션 온라인 슬롯에 @HiltAndroidApp 애너테이션을 지정한다. 그러면 해당 애플리케이션 온라인 슬롯는 Hilt의 컴포넌트들을 애플리케이션의 생명주기에 맞춰 관리하는 루트 컴포넌트 역할을 한다.
Hilt는 다음 안드로이드 컴포넌트에 온라인 슬롯 주입을 지원한다. 애너테이션도 함께 표시했다.
Application: @HiltAndroidApp
ViewModel: @HiltViewModel
Activity: @AndroidEntryPoint
Fragment: @AndroidEntryPoint
View: @AndroidEntryPoint
Service: @AndroidEntryPoint
BroadcastReceiver: @AndroidEntryPoint
애플리케이션 온라인 슬롯에서 Hilt를 설정했기 때문에 @AndroidEntryPoint 애너테이션을 설정한 안드로이드 컴포넌트에 의존성 주입을 할 수 있다. 그러면 다음으로 의존성 주입 대상에 애너테이션을 설정할 차례다.
4)UserRepository
UserRepository는 인터페이스다.인터페이스는 직접 인스턴스를 생성할 수 없기 때문에 별도의 모듈을 만들어서 Hilt에게 인터페이스의 구현체를 어떻게 생성하고 제공해야 하는지 알려줘야 한다.
@Qualifier는 사용자가 직접 정의한 애너테이션을 생성하여 종속성을 구별할 때 사용한다. @InstallIn(SingletonComponent::class)은 UserRepositoryModule이 애플리케이션의 생명주기와 동일한 싱글톤 컴포넌트에 설치되어야 한다는 것을 나타낸다. @Module은 이 객체가 Hilt에게 온라인 슬롯을 제공하는 모듈이라는 것을 알려준다. 모듈은 온라인 슬롯을 제공하는 메서드의 집합이다.
@Module: Hilt에게 의존성을 제공하는 온라인 슬롯를 정의한다. 이 온라인 슬롯 내에서 @Provides 또는 @Binds 애너테이션을 사용하여 의존성을 제공하는 메서드를 정의할 수 있다. @Module 애너테이션이 붙은 온라인 슬롯는 Hilt가 의존성을 관리할 수 있도록 정보를 제공한다.
@InstallIn: @Module이 적용된 온라인 슬롯에 어떤 Hilt 컴포넌트의 생명주기에 설치될 것인지 지정한다.
@Singleton: 애플리케이션의 생명주기 동안 단 하나의 인스턴스만 생성되어 사용된다. 싱글톤으로 애플리케이션 전체에서 공유된다.
475
@Provides: 구체적인 인스턴스 생성 로직을 포함하는 메서드에 사용된다.
@Qualifier: 동일한 타입의 여러 온라인 슬롯 중에서 특정 온라인 슬롯을 주입할 때 사용한다.
5)UserViewModel
@HiltViewModel은 ViewModel의 시작점을 Hilt에게 알린다. @Inject constructor는 온라인 슬롯의 생성자에 @Inject 애너테이션을 사용하여 Hilt가 이 온라인 슬롯의 인스턴스를 생성할 때 필요한 의존성을 자동으로 주입하도록 한다. 현재 의존성 주입 대상은UserRepository다.UserRepository는 인터페이스이고 앞서 모듈을 만들었기 때문에 Hilt가 구현체를 생성하는 법과 어떤 구현체를 제공해야 하는지 알고 있다. 따라서 자동으로 적절한 온라인 슬롯 주입이 이뤄진다.
6)UserService
UserViewModel과 차이점은 ViewModel이 아니라는 것이다. 온라인 슬롯 주입받는 구현체도 다르다.
7) MainActivity
@AndroidEntryPoint 애너테이션은 액티비티가 시작하는 지점을 Hilt에게 알려주는 역할이다. 이제 Hilt가 해당 액티비티에 온라인 슬롯을 주입할 수 있다.
@Inject는 Hilt에 의해 관리되는 의존성을 이 온라인 슬롯에 주입하도록 한다. 이렇게 @Inject를 통해 Hilt에서 삽입하는 필드는 private가 될 수 없다.