본문 바로가기
Android/Hilt

[Android/Hilt] @Provides vs @Binds: Hilt에서 객체를 주입하는 두 가지 방식

by quessr 2025. 3. 7.

 

이전에 사이드 프로젝트를 진행하면서 의존성 주입을 할 때 어떤 곳에는 @Provides를 사용하고, 어떤 곳에는 @Binds를 사용하는 것을 보고 그 차이가 궁금했습니다.

당시에는 명확하게 이해하지 못한 채 사용했었지만, 학습을 진행하며 그 차이를 조금씩 이해하게 되었습니다.

이에 대한 내용을 정리하여 보다 명확하게 개념을 정리하고자 합니다.


@Provides@Binds의 핵심 차이점

구분 사용 방식 특징
@Provides 메서드 내에서 인스턴스를 반환 인터페이스뿐만 아니라 일반 클래스 객체도 생성 가능
@Binds 구현체를 바인딩하는 방식 인터페이스와 구현체 간의 관계를 선언적으로 설정

@Provides를 사용하는 경우

@Provides는 Hilt가 직접 @Inject를 붙여 생성할 수 없는 경우 사용해야 합니다.

사용하는 경우:

  • 외부 라이브러리 객체 (Retrofit, Room, OkHttp 등) 제공
  • 객체 생성 로직이 필요한 경우 (조건문, 빌더 패턴, 팩토리 패턴 등)
  • 객체를 생성하는 데 매개변수가 필요한 경우

@Provides 예제 (클래스 인스턴스를 반환)

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    
    @Provides
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .build()
    }
}
  • Retrofit은 외부 라이브러리이므로, @Inject constructor를 사용할 수 없습니다.
  • 따라서 직접 Retrofit 객체를 생성해서 Hilt에 제공해야 하므로 @Provides를 사용합니다.

@Binds를 사용하는 경우

@Binds는 Hilt가 @Inject로 직접 생성할 수 있는 구현체를 인터페이스와 연결할 때 사용해야 합니다.

사용하는 경우:

  • 인터페이스와 구현체를 1:1로 연결할 때
  • Hilt가 직접 생성할 수 있는 경우 (@Inject constructor 사용 가능)
  • 불필요한 객체 생성을 피하고, 선언적으로 의존성을 바인딩하고 싶을 때

@Binds 예제 (인터페이스의 구현체를 바인딩)

interface ExampleRepository {
    fun fetchData(): String
}

class ExampleRepositoryImpl @Inject constructor() : ExampleRepository {
    override fun fetchData(): String = "Hello from Repository"
}

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
    
    @Binds
    abstract fun bindExampleRepository(
        impl: ExampleRepositoryImpl
    ): ExampleRepository
}
  • ExampleRepositoryImpl@Inject constructor가 있으므로 Hilt가 직접 생성할 수 있습니다.
  • 따라서 @Provides를 사용할 필요 없이 @Binds로 인터페이스와 구현체만 연결하면 됩니다.
  • @Binds를 사용하면 불필요한 객체 생성 로직 없이 더 간결한 코드가 됩니다.

@Provides@Binds의 동작 방식 차이점

@Provides 방식

@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {
    
    @Provides
    fun provideExampleRepository(impl: ExampleRepositoryImpl): ExampleRepository {
        return impl
    }
}
  • Hilt가 ExampleRepositoryImpl을 생성 (@Inject constructor가 있어야 함)
  • @Provides 메서드의 매개변수로 impl: ExampleRepositoryImpl이 주입됨
  • implExampleRepository 타입으로 반환하여 Hilt의 의존성 그래프에 추가
  • ExampleRepository가 필요한 곳에서 이 구현체(ExampleRepositoryImpl)가 주입됨

즉, @Provides는 Hilt 그래프에서 ExampleRepositoryImpl을 받아와서, 반환값을 ExampleRepository 타입으로 제공하는 역할을 합니다.

@Binds 방식

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
    
    @Binds
    abstract fun bindExampleRepository(
        impl: ExampleRepositoryImpl
    ): ExampleRepository
}
  • Hilt가 ExampleRepositoryImpl을 생성 (@Inject constructor가 있어야 함)
  • @Binds는 새로운 객체를 만들지 않고, Hilt에게 "ExampleRepository는 ExampleRepositoryImpl을 사용해야 한다"라고 선언함
  • ExampleRepository가 필요한 곳에서 ExampleRepositoryImpl이 주입됨

즉, @Binds는 Hilt에게 ExampleRepositoryImplExampleRepository의 구현체임을 "연결"하는 역할만 수행합니다.

  • 객체를 생성하지 않기 때문에 @Binds를 사용하는 것이 더 효율적입니다.

최종 정리

사용 상황 @Provides @Binds
객체를 직접 생성해야 하는 경우
외부 라이브러리(Retrofit, Room 등) 사용
인터페이스와 구현체를 연결하는 경우 가능하지만 비효율적
Hilt가 직접 객체를 생성할 수 있는 경우

결론

  • 외부 라이브러리처럼 @Inject를 붙일 수 없는 경우 → @Provides 사용
  • 구현체가 @Inject로 이미 Hilt에 등록되어 있고, 인터페이스와 연결만 하면 되는 경우 → @Binds 사용
  • 객체를 직접 생성해야 하거나, 생성 과정이 필요한 경우 → @Provides 사용
  • Hilt가 생성할 수 있는 경우에는 @Binds를 사용하여 불필요한 객체 생성을 줄이는 것이 좋습니다.
반응형