Android 프로젝트에서 DI를 사용한다고 하면 많은 사람들이 Hilt를 사용하는 것 같다. (가끔 Dagger만 그대로 사용할 수도..?)
필자 또한 그 사람 중에 하나였다. Dagger나 Hilt를 올바르게 사용해본 사람이라면 대부분 겪는 귀찮음불편함이 있을 것이라고 생각한다. 바로 @Binds
를 사용하는 부분이다. 간단하게 이야기 하자면 Dagger Hilt에서 직접 만든 Class를 의존성 주입 받기 위해서 아래와 같이 코드를 작성한다.
class TestUseCaseImpl @Inject constructor(
private val testRepository: Repository
) : TestUseCase {
...
}
그리고 위와 같은 TestUseCase
를 사용하고 싶은 부분에서는 아래와 같이 코드를 작성한다.
@HiltViewModel
class TestViewModel @Inject constructor(
private val testUseCase: TestUseCase
) : ViewModel() {
...
}
하지만 이렇게만 작성하면 빌드 실패한다. 왜냐하면 TestUseCaseImpl
을 TestUseCase
로 형변환 해주는 부분이 없기 때문에 ViewModel에서 TestUseCase
를 갖고 올 수 없다는 에러와 함께 빌드를 실패한다. 다음과 같이 코드를 작성하면 TestUseCaseImpl
에서 TestUseCase
로 형변환이 되고 의존성 주입을 받을 수 있다.
@Module
@InstallIn(SingletonComponent::class)
abstract class UseCaseModule {
@Binds
abstract fun bindTestUseCase(useCase: TestUseCaseImpl): TestUseCase
}
이제 빌드하면 ViewModel에서 정상적으로 TestUseCase
를 받아서 사용할 수 있게된다. 하지만 여기서 생각해볼 수 있는 것은 위의 @Binds
함수다. 위와 같이 구현체 클래스를 interface로 변환해서 사용하는 형태가 프로젝트 안에 많이 존재할 텐데, 이러한 경우 전부 @Binds
함수를 만들어야 한다는 것이다. 그러면 이제 RepositoryModule
, MapperModule
등등 늘어날 것이다. 이러한 문제점을 해소하고 싶었고, 평소 관심 있었으나 쉽게 도전하지 않았던 annotation-processor 만드는 법에 대해서 공부도 할 겸해서, annotation-processor를 통해 @Binds
함수들을 전부 알아서 만들도록 구현했다.
먼저 사용법은 간단하다. 아래의 코드와 같이 @HiltBinds
를 붙이기만 하면 알아서 형변환 해주는 @Binds
를 annotation-processor가 만들어준다.
interface TestUseCase {
...
}
@HiltBinds
class TestUseCaseImpl @Inject constructor(
private val testRepository: Repository
) : TestUseCase {
...
}
// 아래의 코드를 Annotation-Processor가 만들어 줌.
@Module
@InstallIn(SingletonComponent.class)
abstract class TestUseCaseImpl_BindsModule {
@Binds
public abstract TestUseCase bindTestUseCaseImpl(TestUseCaseImpl target);
}
사실 KAPT
보다 KSP
가 몇 배 더 빠르고 좋다고해서 바로 KSP
를 통해 kotlin 코드를 만들도록 구현해볼까 하다가, 공부하는 단계다 보니 java 코드를 만드는 방법 먼저 공부해보고 그다음에 KSP
도 공부해보려고 한다. 대충 중요한 재료(?)는 다음과 같다. JavaPoet
, AutoService
, AbstractProcessor
, hilt-core
이렇게가 중요한 재료이자 키워드라고 보면 좋을 것 같다.
그리고 솔직히 하나의 클래스 안에 다 작성하는 방법이 있긴한데, 성격상 그렇게 하긴 싫었다. 그래서 핵심 core 부분, module을 만들어 내는 generator 부분, 각종 Ext 부분 이렇게 구성했다.
지금은 Hilt에 대한 Annotation-Processor를 만들어 봤는데, 나중에는 활용할 수 있는 부분이 많을 것 같다.
(단, 너무 많이 사용하면 빌드 타임을 늘리는 단점이 있으니 적당히 활용해야 한다.)
요즘 ServerDriven을 통해 UI를 그려내는 프레임워크 개발 작업 중에 있는데, 이러한 부분에서도 활용할 수 있을 것 같다. Retrofit에 ConverterFactory를 잘 활용하면 API 응답값 json에서 특정 String의 값에 따라서 배열 내의 Object들을 각기 다른 Class로 파싱할 수 있다. 이러한 Class 타입으로 파싱하기 위해서 미리 파싱될 VO Class를 ConverterFactory에 특정 String값과 1대1 매칭으로 설정해주어야 하는데, 이러한 부분에서도 annotation-processor를 활용할 수도 있을 것 같다. VO Class를 직접 ConverterFactory에 설정하는 것이 아니라, annotation-processor와 Dagger Hilt에서 지원하는 map-multibinding을 잘 활용하면 VO Class를 작성하고 특정 어노테이션을 붙이면 알아서 ConverterFactory에 설정될 수 있도록 구현할 수 있을 것 같다.
Example 프로젝트와 함께 자세한 구현 프로젝트는 여기에서 확인할 수 있다.