ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Hilt를 이용한 Dependency Injection(DI) - 1
    안드로이드 2021. 7. 8. 15:41
    반응형

    Hilt 란

    • Hilt는 Android Dependency Injection 라이브러리이다.
    • Android 클래스마다 Container를 제공하고 생명주기를 자동으로 관리함으로써 애플리케이션에서 DI를 사용하는 표준 방법을 제공한다.
    • Dagger를 기반으로 제작되었다.

    Gradle 추가

    루트의 build.gradle에 플러그인을 추가한다.

    buildscript {
        ...
        dependencies {
            ...
            classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
        }
    }

    app/build.gradle에 아래 종속 항목을 추가한다.

    ...
    apply plugin: 'kotlin-kapt'
    apply plugin: 'dagger.hilt.android.plugin'
    
    android {
        ...
    }
    
    dependencies {
        implementation "com.google.dagger:hilt-android:2.28-alpha"
        kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
    }

    Hilt는 Java8의 기능을 사용한다. 프로젝트에서 Java8의 기능을 사용하려면 app/build.gradle에 아래 속성을 추가해준다.

    android {
      ...
      compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
      }
    }

     

    Hilt 애플리케이션 클래스

    Hilt를 사용하는 모든 애플리케이션에서는 아래와 같이 Application 클래스에 @HiltAndroidApp 어노테이션을 추가해야한다.

    @HiltAndroidApp
    class ExampleApplication : Application() { ... }

    HiltAndroidApp 어노테이션을 사용하면 Application 객체의 Lifecycle에 연결되며 이와 관련된 종속 항목을 제공한다. 

     

    Android 클래스에 종속성 삽입

    Application 클래스에 @HiltAndroidApp을 추가하면 @AndroidEntryPoint 어노테이션이 추가된 클래스에 종속 항목을 제공할 수 있다.

     

    Hilt에서 지원하는 안드로이드 클래스는 다음과 같다.

    • Application : @HiltAndroidApp을 사용함
    • ViewModel : @HiltViewModel을 사용함
    • Activity
    • Fragment
    • View
    • Service
    • BroadcastReceiver

    @AndroidEntryPoint를 추가한 안드로이드 클래스를 의존하는 안드로이드 클래스에도 @AndroidEntryPoint 어노테이션을 추가해줘야 한다. 예를 들어 Fragment에 @AndroidEntryPoint를 추가해줬다면 이 Fragment를 사용하는 Activity에도 @AndroidEntryPoint 어노테이션을 추가해줘야 한다.

    @AndroidEntryPoint
    class ExampleActivity : AppCompatActivity() {
    
      @Inject lateinit var analytics: AnalyticsAdapter
      ...
    }

    @Inject 어노테이션을 추가하여 주입할 Dependency 컴포넌트를 추가한다. @Inject를 사용할 때는 private으로 선언하면 안된다.

     

    Hilt 결합 정의

    클래스의 필드에서 @Inject를 통해 주입을 받기 위해서는 해당 클래스가 주입하기 위한 종속 항목인지 알아야한다.

    그러기 위해선 생성자에 @Inject 어노테이션을 추가하여 Hilt에 결합 정보를 제공해줘야 한다.

    class AnalyticsAdapter @Inject constructor(
      private val service: AnalyticsService
    ) { ... }

     

    Hilt 모듈

    생성자를 통해 Dependency를 주입할 수 없는 상황이 있다.

    • Builder 패턴
    • Interface 
    • 외부 라이브러리 사용 등

    이럴 경우 Hilt 모듈을 통해 Hilt에 결합 정보를 제공할 수 있다.

    @Module 어노테이션을 추가하여 Hilt 모듈이라는 것을 알리고 @InstallIn 어노테이션을 추가하여 모듈을 사용하거나 설치할 Android 클래스를 Hilt에 알려준다.

     

    @Binds를 이용한 interface 인스턴스 삽입

    • @Binds를 사용하려면 추상 함수를 사용해야 한다. 
    • Interface의 인스턴스를 제공해야할 때 사용한다.
    • 함수 매개변수는 제공할 구현을 Hilt에 알려준다.
    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
    }

     

    위 코드에서 AnalyticsModule은 Activity에 삽입을 원하기 때문에 @InstallIn(ActivityComponent::class) 를 사용했다. 만약 Application 전체에서 사용할 수 있도록 하려면 @InstallIn(SingletonComponent::class)를 사용해주고 bindAnlyticsService에 @Singleton 어노테이션을 추가해주면 된다.

     

    @Provides를 이용한 인스턴스 삽입

    • 외부라이브러리를 사용하는 경우 사용
    • 빌더 패턴으로 인스턴스를 생성해야 하는 경우 사용
    • Module 클래스에서 @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)
      }
    }

    @Provides 어노테이션이 추가된 함수는 다음 정보를 Hilt에 제공한다.

    • 함수 반환 유형은 어떤 유형의 인스턴스를 제공하는지 제공
    • 함수 매개변수는 해당 유형의 종속항목을 제공
    • 함수 본문은 해당 유형의 인스턴스를 제공하는 방법을 제공. 인스턴스를 제공해야할 때마다 함수 본문 실행

     

    동일한 유형에 대한 여러 결합 제공

    다른 설정, 다른 구현을 사용하고 같은 타입을 return하는 다른 모듈을 여러개 정의해야하는 경우 한정자를 지정하여 식별할 수 있다.

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

    @Qualifier 로 한정자 어노테이션 정의를 해준다.

    @Module
    @InstallIn(ApplicationComponent::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()
      }
    }

     

     

    @Provides 또는 @Binds에 위에서 정의한 한정자 어노테이션을 추가하여 식별할 수 있도록 한다. 

    // As a dependency of another class.
    @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
    }

    필드 또는 매개변수에 한정자 어노테이션을 추가하여 필요한 특정 유형을 삽입할 수 있다.

    한정자 어노테이션을 추가하지 않으면 같은 유형을 return 하기 때문에 어느 것을 사용해야할지 알 수 없기 때문에 오류가 발생하거나 잘못된 종속 항목을 삽입할 수 있다.

    반응형

    댓글

Designed by Tistory.