0. Hilt 의존성 주입 장점

예전에는 ViewModel, Repository, UseCase를 생성할 때 new로 직접 객체를 만들거나 생성자로 넘겨주었어야 함. 어떤 의존성이 필요했는지 혼동하는 등의 문제가 발생할 수 있었음.

Hilt를 사용하면?

@HiltViewModel
class TimerViewModel @Inject constructor(
    private val timerUseCases: TimerUseCases,
    @PrimaryTimer private val primaryTimerRepository: TimerRepository,
    @SubTimer private val subTimerRepository: TimerRepository
)

이처럼 Hilt가 알아서 Repository와 UseCase를 생성해서 주입해주므로 코드가 깔끔해지고, 생성자에서 필요한 의존성을 바로 확인할 수 있음!


1. 그래서 Hilt를 썼는데……

일반적으로 프로젝트를 진행하며 앱을 만들 때는 @singleton 어노테이션을 붙여서 모듈을 작성했다.

타이머를 한 개만 만들 때는 늘 하던 대로, 사실상 관성적으로 Singleton Scope를 활용했다. 애플리케이션 프로세스가 종료될 때 함께 destoryed 되도록 객체의 수명을 제한하고.

바인딩에는 범위가 없다고 한다.

그래서, 범위 없는 바인딩은 호출될 때마다 매번 새 객체를 반환하는데, 매번 같은 객체를 반환해야 하는 경우가 종종 있다! (나의 경우처럼…)

그럴 경우에는 Hilt의 scoped binding을 사용해서 컴포넌트가 특정 바인딩에 대해 항상 같은 객체를 반환하도록 할 수 있고, 바인딩에 scope를 부여하려면 클래스나 메소드에 특정 어노테이션을 붙이면 된다.

출처 : https://jtm0609.tistory.com/

출처 : https://jtm0609.tistory.com/

2. 내 애플리케이션은?

보통 별 문제가 없다면 Singleton으로 두고 했었다.

@Module
@InstallIn(SingletonComponent::class)
abstract class TimerModule {
    /* 인터페이스(TimerRepository)에 구현체(TimerRepositoryImpl)를 바인딩 */
    @Binds
    @Singleton
    abstract fun bindTimerRepository(
        timerRepositoryImpl: TimerRepositoryImpl
    ): TimerRepository
}

기존에 내가 구현했던 구현체는 단일 타이머 기준 한 개였기에 @Binds 어노테이션을 활용해 구현체 인스턴스 하나를 인터페이스로 묶었었다. 단일 구현체 → 단일 타입 매핑에 요긴했다.

다만, 듀얼 타이머는 같은 구현체로 구성된 2개의 인스턴스가 각각 필요했다.

그렇기에 abstact + @Binds 구조는 활용할 수 없었다. Primary Timer와 Sub Timer 각각 새 인스턴스를 생성하도록 명시해야 했기 때문이다.