일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- GitHub
- Codeforces
- TEST
- Rxjava
- Kotlin
- androidStudio
- architecture
- Gradle
- livedata
- Hilt
- Coroutine
- activity
- pandas
- 코루틴
- Python
- 쿠링
- 프로그래머스
- MiTweet
- android
- 코드포스
- ProGuard
- 백준
- boj
- Compose
- MyVoca
- textfield
- AWS
- relay
- 암호학
- Coroutines
- Today
- Total
이동식 저장소
[Hilt] Entry Points 본문
Dagger가 직접 지원하지 않는 클래스에서 객체를 주입받고 싶다면 entry point를 사용해 보자. Entry point는 Dagger가 관리하는 객체 간의 그래프를 참조하기 위한 진입점 역할을 한다.
AndroidEntryPoint
사실 우리는 이미 ``@AndroidEntryPoint``라는 어노테이션을 알고 있다. ``@AndroidEntryPoint``는 Hilt가 미리 정의해 둔 entry point로, Activity나 Fragment 등 주요 Android 클래스에서 Hilt 컴포넌트와 해당 컴포넌트에 설치된 Hilt 모듈에 접근할 수 있게 한다.
그러나 ``AndroidEntryPoint``를 사용해도 Hilt가 지원하지 않는 클래스에서 객체를 주입받을 수는 없다. 이런 경우에는 어쩔 수 없이 entry point를 직접 정의해야 한다.
Entry point 정의하기
Entry point를 직접 정의하는 과정은 다음과 같다.
- 인터페이스를 하나 정의한다.
- 인터페이스에 ``@EntryPoint`` 어노테이션을 붙인다.
- 인터페이스 내부에 필요한 타입을 제공하는 함수를 정의한다.
- 인터페이스를 적절한 컴포넌트에 설치한다.
// From MyVoca
@EntryPoint
@InstallIn(SingletonComponent::class)
interface VocaPersistenceRoomEntryPoint {
fun vocaDao(): VocaDao
}
이때 인터페이스가 설치된 컴포넌트에서 해당 타입을 제공할 수 있어야 한다. 나는 ``SingletonComponent``에 설치된 다른 모듈에서 ``VocaDao`` 타입을 제공하는 함수를 작성해 두었다. 따라서 ``VocaPersistenceRoomEntryPoint``는 해당 바인딩을 참조하여 객체를 제공할 것이다.
``fun`` 앞에 어노테이션을 붙여 객체를 제공할 바인딩을 직접 지정할 수도 있다.
Entry point의 모든 함수는 public이어야 한다. 외부의 Dagger 컴포넌트가 entry point의 함수를 구현하기 때문이다.
Entry point 사용하기
Entry point에 접근하려면 ``EntryPoints.get()`` 함수를 사용하자.
val entryPoint = EntryPoints.get(applicationContext, VocaPersistenceRoomEntryPoint::class.java)
val dao = entryPoints.vocaDao()
다만 ``Application`` 등 Android 객체에서 컴포넌트를 가져오는 경우 ``EntryPointAccessors``의 함수를 사용하는 편이 적절하고, 타입 안전성도 얻을 수 있다.
val entryPoint = EntryPointAccessors.fromApplication(applicationContext, VocaPersistenceRoomEntryPoint::class.java)
val dao = entryPoint.vocaDao()
실전 사용 예시
MyVoca에 직접 작성한 코드이다.
// 이 객체 자체도 Hilt로 주입할 수 있게 @Singleton과 생성자 주입을 적용했다.
@Singleton
class VocaPersistenceRoom @Inject constructor(@ApplicationContext context: Context) :
VocaPersistence, CoroutineScope {
// EntryPoint 정의
@EntryPoint
@InstallIn(SingletonComponent::class)
interface VocaPersistenceRoomEntryPoint {
fun vocaDao(): VocaDao
}
private lateinit var vocaDao: VocaDao
// 생성자에서 entry point에 접근한다.
init {
synchronized(this) {
val entryPoint = getVocaPersistenceRoomEntryPoint(context)
assignDao(entryPoint)
}
}
private fun getVocaPersistenceRoomEntryPoint(context: Context) =
EntryPointAccessors.fromApplication(context, VocaPersistenceRoomEntryPoint::class.java)
private fun assignDao(entryPoint: VocaPersistenceRoomEntryPoint) {
vocaDao = entryPoint.vocaDao()
}
}
왠지 ``synchronized``를 사용해야 할 것만 같았다. 클래스 자체가 singleton이기도 하고, entry point에서 생성하는 객체가 매우 비싸기 때문에 객체 할당 부분을 단 한 번만 실행하고 싶었다. 유효한 논리인지는 잘 모르겠다만..
Entry point를 어디에 정의해야 하는가?
일반적으로 entry point는 객체를 주입받고 싶어하는 클래스에 정의해야 한다.
예를 들어 위의 코드에서 entry point가 ``VocaDao.kt`` 파일에 정의됐다고 가정하자. ``VocaDao`` 파일을 읽는 사람은 곧 혼란에 빠지게 될 것이다.
``VocaDao``를 어떻게 얻어야 하는가? 이 인터페이스를 구현한 객체를 얻을 수 있나? 아니면 여기 정의된 entry point에 접근하여 얻어야 하나?
둘 다 틀렸지만, 어쨌든 이런 쓸데없는 혼란을 부추길 수 있으니 entry point는 가급적 entry point를 사용할 클래스에 정의하자. 다른 의존성을 쉽게 추가할 수 있기도 하고.
'Primary > Android' 카테고리의 다른 글
Gson에서 null이 반환될 때 with ProGuard (0) | 2022.09.27 |
---|---|
[Android] 디버깅할 때 앱이 느리다면 (0) | 2022.09.25 |
[Hilt] Modules (0) | 2022.08.29 |
[Hilt] ViewModels (0) | 2022.08.29 |
[Hilt] Android Entry Points (0) | 2022.08.11 |