본문 바로가기
Android

[안드로이드] Hilt에서 @Binds와 @Provides의 차이

by JongSeok 2023. 7. 15.

Hilt 라이브러리를 이용해 의존성을 주입하는 경우에 Module에서 abstract class에는 @Binds 어노테이션이, object에는 @Provides 키워드가 붙게 되는데 어떤 이유에서인지 알아보겠습니다.

 

@Binds? @Provides?

결론부터 말하자면 @Binds와 @Provides의 가장 큰 차이는 어떤 인스턴스를 제공하느냐 입니다.

@Binds 어노테이션은 인터페이스 인스턴스, @Provides 어노테이션은 클래스 인스턴스를 제공합니다.

 

@Binds

@Binds는 Hilt에게 인터페이스 인스턴스를 제공하기 때문에 주로 Interface로 정의된 레포지토리(Repository)를 인스턴스로 제공합니다.
좀 더 정확히 말하면 인터페이스는 생성자를 정의할 수 없기 때문에 Repository를 상속받는 구현체(RepositoryImpl)를 Hilt에게 제공합니다. (나는 분명 Hilt에게 Repository만 알려줬는데 얘가 어떻게 RepositoryImpl에서 override한 메소드를 쓸 수 있지.. 똑똑하네..?) 라고 생각했는데 Module에서 정의되고 있었기 때문입니다.

 

@Binds의 반환 타입은 메소드가 제공할 인스턴스 타입, 메소드의 매개변수는 해당 메소드가 제공할 구현체입니다.


아래의 샘플에서 MyRepository는 인터페이스이기 때문에 생성자를 주입할 수 없습니다.

따라서 @Binds 어노테이션을 이용해 abstract 메소드를 정의하고 메소드의 매개변수에 해당 인터페이스의 구현체(MyRepositoryImpl)를 주입합니다. 그리고 반환 타입은 인터페이스 인스턴스를 반환하도록 정의합니다.

@Module
@InstallIn(SingletonComponent::class)
abstract class MyHiltModule {
    @Binds
    @Singleton
    abstract fun bindMyRepository(impl: MyRepositoryImpl): MyRepository
    
    @Binds
    @Singleton
    abstract fun ...
}

이제 Hilt는 MyHiltModules을 통해 MyRepository의 구현체인 MyRepositoryImpl에 접근할 수 있습니다.

 

인터페이스와 인터페이스의 구현체가 여러 개인 경우에도 Module에 얼마든지 @Binds 어노테이션을 추가할 수 있습니다.


@Provides

@Provides는 Hilt에 의존성을 주입하려는 인스턴스 클래스가 외부 라이브러리(Retrofit, OkHttp, Room, DataStore 등)를 사용하는 경우 혹은 빌더 패턴으로 인스턴스를 생성하는 경우에 사용합니다.

 

@Provides의 반환 타입은 메소드가 제공할 인스턴스 타입, 매개변수는 해당 인스턴스의 의존 항목입니다.

대표적으로 Retrofit은 내부적으로 OkHttp에 의존하기 때문에 Retrofit 클래스를 생성하는 메소드의 매개변수로 OkHttp를 넘겨줍니다.

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    private const val BASE_URL = "MY_BASE_URL"

    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .addNetworkInterceptor(HttpLoggingInterceptor())
            .build()
    }

    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .client(okHttpClient)
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    @Provides
    @Singleton
    fun provideApiService(retrofit: Retrofit): RetrofitService {
        return retrofit.create(RetrofitService::class.java)
    }
}

Retrofit과 OkHttpClient 클래스 모두 외부 라이브러리이기 때문에 @Provides 어노테이션을 이용해 정의합니다.

이제 Hilt가 외부 라이브러리까지 의존성을 주입받아 사용할 준비가 되었습니다.

 

Repository를 상속받는 구현체(RepositoryImpl)의 생성자에 @Provides 메소드로 정의했던 RetrofitService를 주입합니다. 

class MyRepositoryImpl @Inject constructor(
    private val retrofitService: RetrofitService
) : MyRepository {

    override fun testMethod() {
        retrofitService.test()
    }
    ...
}

ViewModel에 @HiltViewModel을 명시하고, ViewModel의 생성자에 Repository를 주입해 정의한 구현체를 통해 Model에 접근합니다.

다음 포스팅에서 ViewModel에서 Hilt를 다루는 방법을 더 자세히 공부해 보겠습니다.

728x90
반응형