Hilt는 표준컴포넌트를 제공해주기 때문에 개발자가 직접 컴포넌트를 만들 필요가 없다.(만들 수는 있음)

각 화살표의 방향은 하위 컴포넌트를 가리킨다.
하위 컴포넌트에서 상위 컴포넌트의 의존성에 접근하는 것은 가능하다.
하지만 상위 컴포넌트에서 하위 컴포넌트의 의존성에 접근하는 것은 불가능하다.
안드로이드 컴포넌트에 대응 되는 Hilt 컴포넌트
| Component | Injector for |
|---|---|
| SingletonComponent | Application |
| ViewModelComponent | ViewModel |
| ActivityComponent | Activity |
| FragmentComponent | Fragment |
| ViewComponent | View |
| ViewWithFragmentCompnent | View with @WithFragmentBindings |
| ServiceComponent | Service |
Scope가 없으면 매 요청시 새로운 인스턴스를 생성하게 된다.
class UnscopedBinding @Inject constructor() {
}
Scope annotation이 있으면 해당 Hilt 컴포넌트의 수명동안은 매 요청에 동일한 인스턴스 반환을 보장한다.
@FragmentScoped
class ScopedBinding @Inject constructor() {
}
하지만 모든 프래그먼트가 동일한 인스턴스를 공유하지는 않는다.
왜냐하면 프래그먼트별로 개별 바인딩 인스턴스를 사용하기 때문
모듈에서 Scope 지정하기
@Module
@InstallIn(FragmentComponent::class)
object FooModule{
// 스코프 없음
@Provides
fun provideUnscopedBinding() = UnscopedBinding()
// 스코프 있음
@Provides
@FragmentScoped
fun provideScopedBinding() = ScopedBinding()
}
Scope annotation은
1. 어떤 의존성을 인스턴스화하는 비용이 클 때
2. 동일한 인스턴스 반환을 원할 때
3. 특정 인스턴스를 공유하고 싶을 때
써야 한다.
Hilt에서 기본으로 제공되는 바인딩
| Component | Defualt Bindings |
|---|---|
| SingletonComponent | Application |
| ActivityRetainedComponent | Application |
| ViewModelComponent | SavedStateHandle, ViewModelLifecycle |
| ActivityComponent | Application, Activity |
| FragmentComponent | Application, Activity, Fragment |
| ViewComponent | Application, Activity, View |
| ViewWithFragmentCompnent | Application, Activity, Fragment, View |
| ServiceComponent | Application, Service |
Hilt의 Component도 Component와 SubComponent로 이루어져있으며,
엄밀히 보자면 Hilt에서는 SingletonComponent 외에는 전부 SubComponent이다.

안드로이드 클래스에서도 @AndroidEntryPoint로 의존성 주입을 활성화 시킬 수 있다.
@AndroidEntryPoint가 지원하는 타입
1. Activity
2. Fragment
3. View
4. Service
5. BroadcastReceiver (SingletonComponent 사용만)
BroadcastReceiver는 자신만의 컴포넌트를 갖고 있지 않기 때문에 SingletonComponent를 통해서만 의존성을 주입받을 수 있다.
안드로이드 클래스에서 의존성 주입시, 상위 (서브)컴포넌트에도 반드시 @AndroidEntryPoint를 선언해줘야 한다.
@AndroidEntryPoint
class MyActivity: ComponentActivity(){
// MyActivity 실행
}
@AndroidEntryPoint
class MyFragment: Fragment(){
// 의존성 주입
}
프래그먼트에서 의존성 주입을 받는 경우에는 액티비티에도 @AndroidEntryPoint를 붙여줘야 한다.
프래그먼트 instance를 유지할지 말지 결정하는 메서드가 retainInstance인데 이 값을 true로 셋팅하면 화면 전환에도 instance를 유지할 수 있게 된다.
하지만 Hilt를 같이 사용하는 경우에는 컴포넌트에 바인딩 된 instance가 scope annotation을 달았더라도 유지되지 않기 때문에 retainInstance 사용을 권장하지 않는다.
애초에 Hilt가 아니어도 프래그먼트에서 instance를 유지하려는 것 자체가 지금 와서는 안티 패턴이라고 한다.
대신 상위 컴포넌트에 바인딩하거나 뷰모델을 사용하는 방법으로 retainInstance을 대체하면 메모리 관리나 유지보수 측면에도 좋은 방법이 된다고 한다.
@AndroidEntryPoint
@WithFragmentBindings
class MyView: View() {
@Inject lateinit var bar: Bar
}
컴포즈를 사용하는 프로젝트에서는 @WithFragmentBindings를 사용할일이 없다.
View를 확장하고 커스텀 뷰를 만드는 경우에도 사용된다.
프로그램을 구성하는 요소, 관련된 데이터와 함수를 하나로 묶은 단위
보통 하나의 소스파일에 모든 기능을 작성하지 않고, 기능별로 모듈을 구성한다.
이러한 모듈을 합쳐서 하나의 실행 가능한 프로그램을 만든다.

Dagger와 Hilt에서는 컴파일 타임에 Component에 Module을 설치하는 방식으로 의존성 주입 셋팅이 이루어진다.
모듈이 설치되는 과정
컴파일 타임, annotation 처리 -> @Module 탐색 -> @InstallIn에서 설치될 컴포넌트 탐색 -> 해당 컴포넌트에 설치
@Module
@InstallIn(SingletonComponent::class)
object FooModule {
@Singleton // 애플리케이션이 끝날 때까지 Bar는 메모리에 남게 된다.(메모리 누수 유발 효과)
@Provides
fun provideBar(app: Application): Bar {
app
// SingletonComponent는 기본 바인딩으로 Application 제공한다.
// 모듈에서 @Provides가 붙은 함수는 Application을 파라미터로 받고 자신이 설치될 Component 바인딩에 접근이 가능하다
}
}
@InstallIn annotation이 없는 경우에는 컴파일 타임에 에러가 발생하게 된다.
@Module를 선언하면 반드시 @InstallIn도 선언해줘야 한다.
@InstallIn(ViewComponent::class, ViewWithFragmentComponent::class)