이전 포스팅에서 Dagger에 대해 공부했었다. Dagger는 범용 DI 프레임 워크로, Android에서 사용하기 위해서는 많은 개념과 이해를 가지고 있어야 한다. Hilt는 Dagger2는 Android 전용 DI 라이브러리로 Dagger에 비해 낮은 러닝커브와 빠르게 초기 DI 환경 세팅을 할 수 있는 강점을 가지고 있다.
Dagger와 Hilt의 차이점을 기준으로 Hilt의 주요 개념
Component : Dagger
컴포넌트에 설치되는 모듈 컴포넌트를 정의하고, 멀티 컴포넌트를 위해서 따로 subComponents를 정의해야 한다.
컴포넌트에 생성자를 정의하고, 주입되는 메소드를 모두 정의해야 한다.
@Subcomponent
interface GameComponent {
// component를 activity에서 어떻게 생성할지를 알려줌.
@Subcomponent.Factory
interface Factory{
fun create(): GameComponent
}
// gameFragment를 injection해서 해당 컴포넌트에 주입해서, gameFragment를 사용할 수 있음.
fun inject(gameFragment: GameFragment){
}
}
Component : Hilt
Hit는 Android 구성 요소들 안에서 별도 설정 없이 사용이 가능하게 하며, 자동으로 생명 주기를 관리한다.
컴포넌트 설정 및 계층 구조 설정이 없음 : 전체 scope동안 살아남게 하려면 Singleton으로 선언하도록 정의하는 방식.
Module : Dagger, Hilt
기존 Dagger는 컴포넌트에 모듈을 명시 해줘야 한다. 각 모듈이 어느 컴포넌트에 설치되었는지를 숙지하고 있어야 하는 단점이 있었으며, 이에 따라 실사용되지 않는 모듈이 존재할 수 있었다. Hilt의 경우는 반대로 모듈에 컴포넌트를 @InstallIn 어노테이션을 추가하는 방식을 가지고 있다.
@Module
@InlstallIn(SingletonComponent::class)
class ProdNewsModule {
@Provides
fun provideProdNewsApi(newsApi: ProdNewsApi): NewsApi = newsApi
}
AndroidEntryPoint
표준 안드로이드 컴포넌트(Activity, Fragment, View, Service, BroadCastRecevier)에 설정하며, 주입은 super.oncreate()에서 발생한다.
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
}
}
EntryPoints
Entry Point는 Dagger + Hilt 가 직접 주입할 수 없는 곳(ContentProvider)에서 유용하게 사용 가능 하다.
모듈과 비슷하게 @InstallIn을 이용해 컴포넌트를 지정하며, EntryPoint.get()으로 접근이 가능하다.
class MyContentProvider : ContentProvider {
fun doSomeFoo() {
val foo = EntryPoint.get(appContext, FooEntryPoint::class).foo()
}
}
@EntryPoint
@InstallIn(SingletonComponent::class)
interface FooEntryPoint {
fun foo(): Foo
}
HiltViewModel : Dagger, Hilt
Dagger2에서 멀티바인딩을 활용하여 ViewModel을 식별해줄 Key를 @MapKey를 통해 선언한다.
ViewModelProvider.Factory를 상속받은 ViewModelFactory 클래스를 정의하였다. 해당 클래스에서 생성자 주입을 통해 얻은 providerMap에서 ViewModelKey로 조회한 Viewmodel을 생성하도록 구현하였다.
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
@Module
interface ViewModelBuilder{
@Binds
fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
class ViewModelFactory @Inject constructor(
private val providerMap : @JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var provider: Provider<out ViewModel>? = providerMap[modelClass]
if(provider == null){
for((key, value) in providerMap) { // 모두 탐색
if(modelClass.isAssignableFrom(key)){
provider = value
break
}
}
}
requireNotNull(provider){ "Unknown ViewModel class : $modelClass"}
return provider.get() as T
}
}
ViewModel을 사용할 Fragment에서는 viewModelFactory를 주입받아 ViewModel을 생성하였다.
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private val viewModel : GameViewModel by viewModels { viewModelFactory }
Hilt에서는 Dagger2와 동일하게 멀티 바인딩을 통해 ViewModel을 주입 받는다. 그러나 방식에서 차이가 발생한다. Hilt는 주입하는 ViewModel 클래스에 @HiltViewModel을 선언하여, ViewModel 모듈을 자동으로 생성해준다.
@HiltViewModel
class GameViewModel @Inject constructor(
private val stateHandler: SavedStateHandle, // hiltViewModel이 자동으로 주입해줌.
private val repository : GameRepository
): ViewModel() {
...
}
사용하는 Fragment에서는 @AndroidEntryPoint를 사용하여 간단하게 ViewModel을 생성할 수 있다.
@AndroidEntryPoint
class GameFragment : Fragment() {
private val viewModel : GameViewModel by viewModels()
}
Dagger Hilt에 대해서
Hilt를 공부하기 위해서 하나의 예제 프로젝트에 Dagger와 Hilt 모두 적용하면서 공부하였다. Hilt는 Dagger에 비교하여 안드로이드 친화적으로 깔끔하고 완성도 있는 DI환경을 제공한다는 느낌을 받았다. 앞으로의 프로젝트에서도 Hilt를 적극 도입할 생각이며, Hilt의 다음 기능이 기대된다.