Photo by Anis Rahman on Unsplash
프로젝트에 Hilt를 적용하다 막히셨나요? 🤷♂️🤷♂️
Hilt에 사용되는 @Binds, @Provides 적용에
도움되는 내용을 준비했습니다.
(도움이 되었으면... 좋겠네요..❗️)
지난번에 Hilt적용 방법과 간단한 내용에 대하여 알아봤습니다.
✔️ Hilt 모듈 (@Binds, @Provides)
✔️ @InstallIn
✔️ Qualifier / Predefined qualifiers
✔️ Hilt 모듈 (@Binds, @Provides)
지난번 적용했던 Hilt 의존성 주입을 하지 못하는 경우도 있습니다.
예를들면 인터페이스를 생성자 주입하지 못하고
외부 라이브러리 Retrofit 클래스 같은 경우
말 그대로 외부 라이브러리 이기때문에 프로젝트 내부에서 직접적으로 종속 관계 삽입이 불가능 합니다.
이럴 때는 Hilt모듈을 이용하여 Hilt에 결합 정보를 제공할 수 있습니다.
✔️ @Binds
interface MyService {
fun getMyMethods(str: String)
}
위와 같은 인터페이스를 생성하고 MyService를 상속받는 Class들을 생성하여 getMyMethods를 사용하려고 합니다.
class MyServiceRepository @Inject constructor(
private val weatherRepository: WeatherRepository
): MyService {
override fun getMyMethods(str: String) {
weatherRepository.getData(str)
}
}
class MySecondServiceRepository @Inject constructor(
private val weatherRepository: WeatherRepository
): MyService {
override fun getMyMethods(str: String) {
weatherRepository.getDataList(str)
}
}
MyService를 상속받아 사용하는
MyServiceRepository, MySecondServiceRepository가 있습니다.
@HiltViewModel
class MainViewModel @Inject constructor(
private val myServiceRepository: MyService
): BaseViewModel() {
...
myServiceRepository.getMyMethods(str)
}
생성자에 myServiceRepository를 받아 getMyMethods를 호출하고 있습니다.
얼핏 보기에는 아무 문제없이 보이는 소스이지만
여기에는 큰 문제가 하나 있습니다.
MyService는 인터페이스 입니다. 어디서 누구든 상속받을수 있죠!
MainViewModel에서 생성자의 myServiceRepository: MyService를 보면
Hilt는 혼란스러울 것 입니다.
왜냐면 MyService를 상속받은
MyServiceRepository, MySecondServiceRepository 둘중에 선택할 수 없기 때문입니다.
여기서 오늘의 키워드 @Binds가 활약을 합니다.
@Module
@InstallIn(SingletonComponent::class)
abstract class MyModule {
@Binds
@Singleton
@MyRepository
abstract fun bindMyRepository(impl: MyServiceRepository): MyService
@Binds
@Singleton
@MySecondRepository
abstract fun bindMySecondRepository(impl: MySecondServiceRepository): MyService
}
@Qualifier
annotation class MyRepository
@Qualifier
annotation class MySecondRepository
.
위와 같이 @Qualifier를 사용한 annotation class를 정의한 후
@Binds와 같이 정의한 @MyRepository 또는 @MySecondRepository를 통하여
상속받은 MyService를 구분 시킵니다.
(고객님 당황하셨죠?? 😅 갑자기 등장한 @InstallIn과 @Qualifier는 아래에서 다뤄보겠습니다.)
@HiltViewModel
class MainViewModel @Inject constructor(
@MySecondRepository private val myServiceRepository: MyService // 또는 @MyRepository
): BaseViewModel() {
...
myServiceRepository.getMyMethods(str)
}
이제 다시 MainViewModel의 생성자 앞에 정의해준 annotation을 사용하여
MyServiceRepository 또는 MySecondServiceRepository를 구분하여 주면
끝!!!
어때요 참 쉽죠?! 👨🎨🎨
(사실 저도 이해하는데 오랜 시간이 걸렸습니다 ㅠㅠ...)
✔️ @Provides
@Binds가 인터페이스에 사용되었다면
@Provides는 외부 라이브러리에 자주 사용됩니다.
외부 라이브러리에 경우 생성한 프로젝트 안에서 구현된 것이 아니기 때문에
직접적인 종속관계 형성에 어려움이 있습니다.
그렇기 때문에 @Provides를 사용합니다.
Android에서 자주 사용되는 통신 라이브러리 Retorfit 사용 예제를 준비했습니다.
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun httpLoggingInterceptor(): LoggingInterceptor =
LoggingInterceptor.Builder() // 전송로그
.setLevel(Level.BASIC)
.log(Platform.INFO)
.tag("log")
.build()
@Provides
@Singleton
fun provideOkHttpClient(httpLoggingInterceptor: LoggingInterceptor): OkHttpClient =
if (PRINT_LOG) {
OkHttpClient.Builder()
.cookieJar(JavaNetCookieJar(CookieManager())) // 쿠키 매니저 연결
.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS) // 쓰기 타임아웃 시간 설정
.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS) // 읽기 타임아웃 시간 설정
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS) // 연결 타임아웃 시간 설정
.cache(null) // 캐시사용 안함
.addInterceptor { chain ->
chain.proceed(
chain.request()
.newBuilder()
.build()
)
}
.addInterceptor(httpLoggingInterceptor)
.addNetworkInterceptor(StethoInterceptor()) // Stetho 로그
.build()
}
/**
* 기본 설정하여 Retrofit을 반환
*
* @return 설정이 반영된 Retrofit
*/
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit =
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // Rx를 사용할 수 있도록 아답터 적용
.addConverterFactory(ScalarsConverterFactory.create()) // ScalarConverter 적용
.addConverterFactory(GsonConverterFactory.create()) // GsonConverter 적용
.build()
@Provides
@Singleton
fun baseApi(retrofit: Retrofit): BasicApi = retrofit.create(BasicApi::class.java)
}
interface BasicApi {
@GET
fun getApi(
@Url url: String,
@QueryMap params: Map<String, Any?> = mapOf(),
@HeaderMap headers: Map<String, Any?> = mapOf()
): Flowable<JsonObject>
}
class WeatherRepository @Inject constructor(
private val baseApi: BasicApi,
private val weatherNowDao: WeatherNowDao
) {
...
fun getNow(
params: Map<String, Any?> = mapOf(),
header: Map<String, Any?> = mapOf()
): Flowable<JsonArray> {
return baseApi.get( // REST API GET 방식 호출
...
}
...
}
저와 같은 경우 WeatherRepository에서 BasicApi를 받아 GET 방식으로 API 호출하고 있습니다.
✔️ @InstallIn
드디어 위의 예제에서 지속적으로 나왔던
@InstallIn에 대하여 이야기 하려고 합니다.
@InstallIn 어노테이션을 사용하여 해당 Hilt 모듈이 사용될
Android Class를 Hilt에 알려주는 역할을 합니다.
@Module
@InstallIn(ActivityComponent::class)
class ModuleClass {
...
}
예를들어 위와같이 소스가 있다면
해당 Hilt모듈은 Android Activity에서 사용될 모듈임을 선언한 것 입니다.
✔️ Qualifier / Predefined qualifiers
@Qualifier의 경우 위에서 @Binds를 살펴볼때 나왔습니다.
@Module
@InstallIn(SingletonComponent::class)
abstract class MyModule {
@Binds
@Singleton
@MyRepository
abstract fun bindMyRepository(impl: MyServiceRepository): MyService
@Binds
@Singleton
@MySecondRepository
abstract fun bindMySecondRepository(impl: MySecondServiceRepository): MyService
}
@Qualifier
annotation class MyRepository
@Qualifier
annotation class MySecondRepository
Hilt는 동일한 유형에 대해 여러 결합자를 제공합니다.
Hilt 구현체가 중복될 경우
@Qualifier를 통하여 구분시켜 주입을 시도합니다.
이를 한정자라고 하며
사전의 정의되어 있는 한정자는 Predefined qualifiers이며
대표적인 예로는 @ApplicationContext, @ActivityContext를 제공합니다.
class AnalyticsAdapter @Inject constructor(
@ActivityContext private val context: Context,
private val service: AnalyticsService
) { ... }
Context 객체가 필요한 경우
상황에 따라 @ApplicationContext, @ActivityContext 한정자를 사용합니다.
여기까지 저의 긴 글을 읽어주셔서 감사합니다.
제가 습관적으로 코딩을 하는 그날까지 습관적으로 코딩을 하기 위해 글 작성을 꾸준하게 해보겠습니다.
'Android > DI' 카테고리의 다른 글
[Android] Hilt @Qualifier - Retrofit 서로다른 baseUrl 구분하기 (2) | 2023.04.27 |
---|---|
[Kotlin] Koin은 DI가 아니다?! (0) | 2023.01.14 |
Android Hilt 적용하기1 - (Koin vs Hilt) (2) | 2022.12.10 |
[Kotlin] Android Koin을 활용하여 DI 적용 (경량화 DI Koin) (0) | 2022.11.25 |
Android DI(Dependency Injection) 의존성 주입이 뭐지? (0) | 2022.11.23 |
최근댓글