본문 바로가기

안드로이드

Dagger의 @Binds vs @Provides, @Qualifier vs @Named, @Lazy<T> vs @Provider<T>

반응형

@Binds

interface AnalyticsService {
  fun analyticsMethods()
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
class AnalyticsServiceImpl @Inject constructor(
  ...
) : AnalyticsService { ... }

@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {

  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

 

인터페이스 자체로 생성자를 생성할 수 없으므로 abstract class에 Binds를 사용하여 주입이 가능하다.
Binds의 경우 AnalyticsServiceImpl 주입하는 부분의 코드를 갈아치워 오버헤드가 적음므로 Provides 대신 Binds를 사용하는게 좋다.

 

@Provides

@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    // Potential dependencies of this type
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

 

유형을 삽입하는 경우가 Interface를 사용하는게 아닌 외부의 라이브러를 통해 객체를 가져오는 경우 직접 객체를 생성해주어야 하는데 이때 Provides를 사용하면 된다. 하지만 Provides의 경우 메소드를 따로 생성함으로 Binds에 비해 오버헤드가 발생한다.

 

@Qualifier

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient

//어노테이션 설정

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

  @AuthInterceptorOkHttpClient
  @Provides
  fun provideAuthInterceptorOkHttpClient(
    authInterceptor: AuthInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(authInterceptor)
               .build()
  }

  @OtherInterceptorOkHttpClient
  @Provides
  fun provideOtherInterceptorOkHttpClient(
    otherInterceptor: OtherInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(otherInterceptor)
               .build()
  }
}

//interceptor를 가진 okhttp와 interceptor를 가지지 못한 okhttp를 Qualifier를 통해 구분해 준다.

@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    @AuthInterceptorOkHttpClient okHttpClient: OkHttpClient
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .client(okHttpClient)
               .build()
               .create(AnalyticsService::class.java)
  }
}

// As a dependency of a constructor-injected class.
class ExampleServiceImpl @Inject constructor(
  @AuthInterceptorOkHttpClient private val okHttpClient: OkHttpClient
) : ...

// At field injection.
@AndroidEntryPoint
class ExampleActivity: AppCompatActivity() {

  @AuthInterceptorOkHttpClient
  @Inject lateinit var okHttpClient: OkHttpClient
}

//구분한 Qualifier를 통하여 원하는 okhttp를 선택하여 할당해 준다.

 

Provides나 Binds를 통하여 객체를 주입할 때 동일한 객체의 생성에 대해서 구분하는 방법이다.
Qualifier를 통해 AuthInterceptorOkHttpClient, AuthInterceptorOkHttpClient등과 같은 어노테이션을 만들어주어 동일하지만 다른 okhttpclient를 제공해 줄 수 있다.

 

@Named

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

  @Named("AuthInterceptor")
  @Provides
  fun provideAuthInterceptorOkHttpClient(
    authInterceptor: AuthInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(authInterceptor)
               .build()
  }

  @Named("OtherInterceptor")
  @Provides
  fun provideOtherInterceptorOkHttpClient(
    otherInterceptor: OtherInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(otherInterceptor)
               .build()
  }
}

//interceptor를 가진 okhttp와 interceptor를 가지지 못한 okhttp를 Named를 통해 구분해 준다.

@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    @Named("AuthInterceptor") okHttpClient: OkHttpClient
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .client(okHttpClient)
               .build()
               .create(AnalyticsService::class.java)
  }
}

// As a dependency of a constructor-injected class.
class ExampleServiceImpl @Inject constructor(
  @Named("AuthInterceptor") private val okHttpClient: OkHttpClient
) : ...

// At field injection.
@AndroidEntryPoint
class ExampleActivity: AppCompatActivity() {

  @Named("AuthInterceptor")
  @Inject lateinit var okHttpClient: OkHttpClient
}

//구분한 Named를 통하여 원하는 okhttp를 선택하여 할당해 준다.

 

Named도 Qualifier와 동일한 기능을 한다. 별도의 어노테이션 선언 없이도 간단히 이용 가능하지만 수정하려면 직접 텍스트를 입려해 주어야 해서 네이밍등의 일괄적인 변경 등 수정 및 확장성이 떨어 짐으로 Qualifier를 이용하는 것이 좋다.

Lazy<T>

   final class LazyCounter {
      @Inject Lazy<Integer> lazy;

     void print() {
       System.out.println("printing...");
       System.out.println(lazy.get());
       System.out.println(lazy.get());
       System.out.println(lazy.get());
     }
   }
 
   printing...
   computing...
   100
   100
   100

 

호출되는 시점에서 데이터를 생성한다.

매번 같은 객체를 리턴해 준다. 

 

캐싱된 데이터가 필요하다면 Lazy를 사용하면 된다. 

 

   final class LazyCounters {
      @Inject LazyCounter counter1;
      @Inject LazyCounter counter2;

     void print() {
       counter1.print();
       counter2.print();
     }
   }
   printing...
   computing...
   100
   100
   100
   printing...
   computing...
   101
   101
   101

모든 클라이언트에게 동일한 객체를 제공하는 @Singleton과 같은 기능을 제공한다고 생각할 수 있지만

위와 같이 연속적으로 Lazy를 호출 시 서로 다른 객체를 생성하는 것을 확인할 수 있다.

 

Provider<T>

   final class ProviderCounter {
      @Inject Provider<Integer> provider;

     void print() {
       System.out.println("printing...");
       System.out.println(provider.get());
       System.out.println(provider.get());
       System.out.println(provider.get());
     }
   }
   printing...
   computing...
   100
   computing...
   101
   computing...
   102

Lazy<T>와 마찬가지로 호출하는 시점에서 데이터를 생성하지만

호출시점 마다 매번 새로운 객체를 생성한다.

 

캐싱된 데이터가 필요하지 않다면 Provier를 사용하면된다.

 

참고

https://dagger.dev/api/2.13/dagger/Lazy.html

https://developer.android.com/training/dependency-injection/hilt-android?hl=ko

반응형