Hilt

노준혁·2023년 2월 12일
0

https://developer.android.com/training/dependency-injection/hilt-android#hilt-modules


  • Hilt
    • 프로젝트에서 boilerplate of doing manual dependency injection를 줄이는 android DI 라이브러리
    • 프로젝트의 모든 Android 클래스에 컨테이너를 제공, 수명 주기를 자동으로 관리함으로써 애플리케이션에서 DI를 사용하는 표준 방법 제공
    • Dagger가 제공하는 compile-time correctness, runtime performance, scalability의 이점을 가져오기 위해 Dagger 기반으로 빌드됨.

  • build-gradle
  1. Apply the Gradle plugin
plugins {
	kotlin("kapt")
	id("com.google.dagger.hilt.android") version "2.44" apply false
  1. Add these dependencies in your app/build.gradle file
android {
	// Hilt는 Java8 기능을 사용하기에 프로젝트에서 Java8 사용 설정
  compileOptions {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
  }
}
 
dependencies {
	implementation("com.google.dagger:hilt-android:2.44")
    kapt("com.google.dagger:hilt-android-compiler:2.44")
}

kapt {
	correctErrorTypes = true
}

  • Hilt application class

    • Hilt를 사용하는 모든 App은 @HiltAndroidApp 어노테이션으로 지정된 Application클래스를 포함해야 함.

    • @HiltAndroidApp은 application-level dependency container 역할을 하는 Application의 기본 클래스를 포함해 Hilt의 코드 생성(code generation)을 트리거함.

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

생성된 Hilt Component는 Application 객체의 lifeCylce에 attach되며 이와 관련한 dependencies들을 제공함. 또한, 이 Application 클래스는 App의 상위 Component이므로 다른 Component는 이 상위 Component에서 제공하는 dependencies들에 액세스할 수 있음.


  • Inject dependencies into Android classes
    • Hilt가 Application class에 설정되고 나면,
      Hilt가 Application-level component를 사용할 수 있게 되며, @AndroidEntryPoint 어노테이션이 있는 다른 Android class에 dependencies들을 제공할 수 있게 된다.
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
	...
}

  • Hilt currently supports the following Android classes:

    • Application (by using @HiltAndroidApp)
    • ViewModel (by using @HiltViewModel)
    • Activity
    • Fragment
    • View
    • Service
    • BroadcastReceiver
  • 만약, Android class(B클래스)에 @AndroidEntryPoint 어노테이션을 지정하면, (A클래스의 생성자로 B클래스를 받을 때)
    B클래스에 의존하는 A클래스 역시 어노테이션을 지정해줘야 함.
    ex)
    if you annotate a fragment(B클래스), then you must also annotate any activities(A클래스) where you use that fragment.

  • @AndroidEntryPoint는 프로젝트의 각 Android class에 관한 개별 Hilt Component를 생성.
    이러한 Component는 Component 계층 구조에 설명된 대로 해당하는 각 상위 class에서 dependencies들을 받을 수 있다.


  • Define Hilt bindings
    field injection을 실행하려면, Hilt가 해당하는 Component로부터 필요한 dependencies의 인스턴스를 제공하는 방법을 알아야 한다. binding에는 특정 타입의 인스턴스를 dependency로 어떻게 제공하는지에 관한 필요한 방법, 정보가 포함된다.

Hilt에 binding 정보(Component로부터 필요한 dependencies의 인스턴스를 제공하는 방법)를 제공하는 한 가지 방법은 Constructor injection.
=> Consturctor injection -> Hilt binding

ex)
---------------------------------
 기본적인 코틀린 생성자 사용하는 ex
---------------------------------
class A(val name: String, val age: Int)
위는 name과 age 프로퍼티를 가진 A 클래스
A의 인스턴스를 val a = A(conatuseus, 30) 이런식으로 간단하게 생성 가능
클래스명 옆의 괄호가 프로퍼티의 선언과 초기화를 같이 함.
이 경우 constructor 키워드가 생략 가능함.

/////////////////////////////////////////////////
----------------------------------------------------------
주 생성자가 어노테이션이나 접근 제어자(private)을 가지면 
constructor 키워드 생략불가.
그래서 @Inject 옆에 constructor를 적어주는 거.
----------------------------------------------------------

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

class의 생성자에서 @Inject 어노테이션을 사용함으로써 Hilt에게 class에 인스턴스를 제공하는 방법을 알려주게 됨.


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

어노테이션이 지정된 class 생성자의 매개변수는 그 클래스의 dependencies들이다.
위 예에선 AnalyticsAdapter가 AnalyticsService를 dependency로 가지고 있음.
따라서 Hilt는 AnalyticsService의 인스턴스를 어떻게 제공하는지에 관한 방법을 알아야 함.


  • Hilt Modules

    • interface는 constructor-inject 할 수 없음.

    • 또한 외부 라이브러리의 class와 같이 소유하지 않은 타입도 constructor-inject할 수 없음.

    • 이럴 때는 Hilt 모듈을 사용하여 Hilt에 Binding infomation을 제공할 수 있음.
      (binding information = Component로부터 필요한 dependencies의 인스턴스를 제공하는 방법)

    • Hilt 모듈은 @Module로 어노테이션이 지정된 class.

    • Dagger 모듈과 마찬가지로 이 모듈은 특정 타입의 인스턴스를 제공하는 방법을 Hilt에 알려준다.

    • 그러나 Dagger 모듈과는 달리, Hilt 모듈에 @InstallIn 어노테이션을 달아 각 모듈이 사용되거나 설치될 Android class를 Hilt에게 알려주어야 함.

    • Hilt 모듈에 제공하는 dependency는 Hilt 모듈을 설치하는 Android class와 연관되어 있는 모든 생성된 component에서 사용할 수 있음.


  • Inject interface instances with @Binds

AnalyticsService가 interface라면 이 interface를 constructor injection 할 수 없다.
대신 Hilt 모듈 내부에 @Binds 어노테이션이 지정된 추상 함수를 생성함으로써 Hilt에 binding information를 제공.
(binding information = Component로부터 필요한 dependencies의 인스턴스를 제공하는 방법)

@Binds 어노테이션은 interface의 인스턴스를 제공해야 할 때 사용해야 할 implementation을 Hilt에 알려줌.

  • 어노테이션이 지정된 함수는 Hilt에 다음 정보들을 제공
  1. 함수 return 타입은 Hilt에게 해당 함수가 어떤 interface의 인스턴스를 제공하는지 알려줌.
  2. 함수 매개변수는 Hilt에게 제공할 implementation을 알려줍니다.
interface AnalyticsService {
  fun analyticsMethods()
}


// Hilt는 AnalyticsServiceImpl 인스턴스도 제공하는 방법을 알아야
// 하므로 Constructor-injection 수행
class AnalyticsServiceImpl @Inject constructor(
  ...
) : AnalyticsService { 
	...
}

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

  // Hilt 모듈 내부에 @Binds 어노테이션이 지정된 추상함수를
  // 생성함으로써 Hilt에게 interface의 binding information을 제공
  @Binds // 사용해야 할 implementation을 Hilt에게 알려줌.
  abstract fun bindAnalyticsService( // AnalyticsService는 interface
    analyticsServiceImpl: AnalyticsServiceImpl // 함수 매개변수는 Hilt에게 제공할 implementation을 알려줌.
  ) : AnalyticsService // 함수 리턴타입은 Hilt에게 해당 함수가 어떤 interface의 인스턴스를 제공하는지 알려줌.
}

개발자는 Hilt를 통해 AnalyticsModule의 dependency를 ExampleActivity에 삽입하기를 원하기 때문에 Hilt 모듈 AnalyticsModule @InstallIn(ActivityComponent::class) 어노테이션을 지정함.
이 @InstallIn(ActivityComponent::class) 어노테이션은 AnalyticsModule의 모든 dependency를 App의 모든 activities에서 사용할 수 있음을 의미.

  • 참고) Hilt provides the following components:
    뒤에서 다시 다룰 예정

  • Inject instances with @Provides

타입을 constructor-injection 할 수 없는 것은 인터페이스만이 아니다.

    1. 외부 라이브러리에서 제공되는 클래스(Retrofit, OkHttpClient 또는 Room DB와 같은 class)의 경우 그 class를 소유하지 못한 상태이기에 constructor-injection이 불가능
    1. builder pattern으로 인스턴스를 생성해야 하는 경우에도 constructor-injection이 불가능

ex)
만약, AnalyticsService class를 직접 소유하지 않는 경우에는
(위 예제에서는 소유하고 있지만, 소유하지 않았다고 가정한다면)
Hilt 모듈 내부에 함수를 생성하고 이 함수에 @Provides 어노테이션을 지정하여 Hilt에게 이 타입의 인스턴스를 제공하는 방법을 알릴 수 있다.

  • 어노테이션이 지정된 함수는 Hilt에게 다음 정보들을 제공.
  1. 함수 return Type은 Hilt에게 함수가 어떤 타입의 인스턴스를 제공하는지 알려줌.
    함수 매개변수는 Hilt에게 해당하는 타입의 dependencies들을 알려줌.
    함수 body는 Hilt에게 해당 타입의 인스턴스를 제공하는 방법을 알려줌.
    Hilt는 해당 타입의 인스턴스를 제공해야 할 때마다 함수 body를 execute함.
@Module
@InstallIn(ActivityComponent.class)
object AnalyticsModule {

// Hilt 모듈 내부에 함수를 생성하고 이 함수에 @Provides 어노테이션을 지정하여
// Hilt에게 이 타입의 인스턴스를 제공하는 방법을 알림.
  @Provides
  fun provideAnalyticsService(
  // 함수 매개변수는 Hilt에게 해당 타입의 dependencies들을 알려줌.
   '''
   Potential dependencies of this type 
   '''
  ) : AnalyticsService { // 함수 리턴타입은 Hilt에게 함수가 어떤 타입의 인스턴스를 제공하는지 알려줌.
  
  // 함수 body는 Hilt에게 해당 타입의 인스턴스를 제공하는 방법을 알려줌.
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

  • Provide multiple bindings for the same type

dependencies와 동일한 타입의 다양한 implementations을 제공하는 Hilt가 필요한 경우에는 Hilt에게 multiple binding을 제공해야 한다.
qualifiers(한정자)를 사용하여 동일한 타입에 대해 multiple bindings을 정의할 수 있다.

qualifiers는 특정 타입에 대해 다수의 binding이 정의되어 있을 때
그 타입의 특정 binding을 식별하는 데 사용하는 어노테이션.

ex)
AnalyticsService Call(호출)을 intercept해야 한다면
interceptor와 함께 OkHttpClient 객체(object)를 사용할 수 있다.
다른 서비스에서는 호출을 다른 방식으로 intercept해야 할 수도 있는데,
이 경우에는 서로 다른 두 가지 OkHttpClient implementation을 어떻게 제공하는지에 관한 방법을 Hilt에게 알려야 한다.

  • 다음과 같이 @Binds 또는 @Provides 메소드에 어노테이션을 지정하는 데 사용할 qualifier한정자를 정의
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient

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

그런 다음, Hilt는 각 qualifer와 일치하는 타입의 인스턴스를 어떻게 제공하는지에 대한 방법을 알아야 함. 이 경우에는 @Provides와 함께 Hilt 모듈을 사용하면 됨. 두 메서드 모두 동일한 리턴타입을 갖지만 qualifers는 다음과 같이 두 가지의 서로 다른 binding으로 메서드에 label을 지정함.

@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()
  }
}

다음과 같이 field 또는 매개변수에 해당 qualifer로 어노테이션함으로써 필요한 특정 타입을 injection할 수도 있음.

// 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
}

qualifier를 타입에 추가한다면, 그 dependency를 제공하는 가능한 모든 방법에 qualifier를 추가하는 것이 좋다. ->
기본 또는 일반 implementation을 qualifier 없이 그대로 두면 에러가 발생하기 쉬우며 Hilt가 잘못된 dependency를 삽입할 수도 있기 때문

  • Predefined qualifiers in Hilt
    Hilt는 몇 가지 미리 정의된 qualifier를 제공함.
    예를 들어 application 또는 activity의 Context Class가 필요할 수 있으므로 Hilt는 @ApplicationContext 및 @ActivityContext qualifier를 제공한다.

만약, 위 예의 AnalyticsAdapter 클래스에서 activity의 context가 필요하다고 가정하면, 다음과 같이 하면 됨.

class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context,
    private val service: AnalyticsService
) { 
	...
}
  • Generated components for Android classes
    field injection을 수행할 수 있는 각 Android class마다 @InstallIn 어노테이션에 참조할 수 있는 관련 Hilt Component가 있음.

각 Hilt Component는 해당하는 Android Class에 binding을 inject하게 된다.

ex) 위 예제 대부분에서는 Hilt 모듈에 ActivityComponent를 사용했음.
Hilt는 다음 Component를 제공한다.


  • Component lifetimes

Hilt는 해당 Android class의 수명 주기에 따라 생성된 Component classes들의 인스턴스를 자동으로 만들고 제거한다.


  • Component scopes
    기본적으로 Hilt의 모든 binding은 범위가 지정되지 않음.
    즉, 앱이 binding을 요청할 때마다 Hilt는 필요한 타입의 새 인스턴스를 생성함.

이 예에서 Hilt는 다른 타입의 dependency로 또는 field injection을 통해(ExampleActivity에서와 같이) AnalyticsAdapter를 제공할 때마다 AnalyticsAdapter의 새 인스턴스를 제공한다.

그러나
Hilt는 특정 Component로 범위를 지정할 수 있도록 binding을 제공할 수도 있다.
Hilt는 binding의 범위가 지정된 Component의 인스턴스마다 한 번만 범위가 지정된 binding을 생성하며, 이 binding에 관한 모든 요청은 동일한 인스턴스를 공유하게 됨.

생성된 각 Component의 Scope 어노테이션은 다음과 같다.

ex)
만약, @ActivityScoped를 사용하여 AnalyticsAdapter의 Scope를 ActivityComponent로 지정하면 Hilt는 해당 Activity의 lifeCycle동안 동일한 AnalyticsAdapter 인스턴스를 제공함.

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

AnalyticsService에 ExampleActivity 뿐만 아니라 앱의 모든 곳에서 매번 동일한 인스턴스를 사용해야 하는 internal state가 있다고 가정한다면,
AnalyticsService의 Scope를 SingletonComponent로 지정하는 것이 적절.
결과적으로 Component는 AnalyticsService의 인스턴스를 제공해야 할 때마다 매번 동일한 인스턴스를 제공하게 됨.


Hilt 모듈에서 어떻게 binding의 Scope를 Component로 지정하는지는 다음과 같다.
binding의 Scope는 binding이 install된 Component의 Scope와 일치해야 하므로 이 예에서는 ActivityComponent 대신 SingletonComponent에 AnalyticsService를 install해야 된다.


// If AnalyticsService is an interface.
@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

  @Singleton // @InstallIn(SingletonComponent::class)와 일치해야 함.
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {

  @Singleton // @InstallIn(SingletonComponent::class)와 일치해야 함.
  @Provides
  fun provideAnalyticsService(): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

  • Component hierarchy

Component에 Module을 install하면 이 Component의 다른 bindings 또는 Component 계층 구조에서 그 아래에 있는 하위 Component의 다른 binding의 dependency로 install된 Module의 binding에 Access 할 수 있다.


  • Component default bindings

각 Hilt Component는 Hilt가 고유한 custom binding에 dependency로 삽입할 수 있는 default binding set와 함께 제공됨.
이러한 binding은 general Activity 및 Fragment Type에 해당하며 특정 SubClass에는 해당되지 않는다. -> 이는 Hilt가 모든 Activity를 삽입하는 데 single activity Component 정의를 사용하기 때문. 각 Activity에는 이 Component의 다른 인스턴스가 있음.

  • Application context binding은 @ApplicationContext를 통해 사용 가능
class AnalyticsServiceImpl @Inject constructor(
  @ApplicationContext context: Context
) : AnalyticsService {
	... 
}

// The Application binding is available without qualifiers.
class AnalyticsServiceImpl @Inject constructor(
  application: Application
) : AnalyticsService { 
	...
}
  • Activity context binding은 @ActivityContext를 통해 사용 가능
class AnalyticsAdapter @Inject constructor(
  @ActivityContext context: Context
) { 
	...    
}

// The Activity binding is available without qualifiers.
class AnalyticsAdapter @Inject constructor(
  activity: FragmentActivity
) {
	...
}

  • Inject dependencies in classes not supported by Hilt
    Hilt에는 가장 일반적인 Android Class에 관한 지원이 함께 제공된다.
    그러나 Hilt가 지원하지 않는 클래스에 field injection을 실행해야 할 수도 있는데,
    이러한 경우 @EntryPoint 어노테이션을 사용하여 entry point(진입점)을 만들 수 있다.
    entryPojnt는 Hilt가 관리하는 코드와 그렇지 않은 코드 사이의 경계이다. 즉, Hilt가 관리하는 객체의 graph에 코드가 처음 들어가는 지점.
    entryPoint를 통해 Hilt는 Hilt가 관리하지 않는 코드를 사용하여 dependency graph 내에서 dependencies들을 제공할 수 있음.

예를 들어,
Hilt는 content providers를 직접 지원하지 않는데, Hilt를 사용하여 일부 dependecies들을 가져오도록 content providers를 사용하기 원한다면,
원하는 binidng type 마다 @EntryPoint 어노테이션이 지정된 interface를 정의하고 qulifiers를 포함해야 한다.
그리고 다음과 같이 @InstallIn을 추가하여 entryPoint를 install할 Component를 지정.

class ExampleContentProvider : ContentProvider() {

  @EntryPoint
  @InstallIn(SingletonComponent::class)
  interface ExampleContentProviderEntryPoint {
    fun analyticsService(): AnalyticsService
  }
   
   ...
}

EntryPont에 Access하려면 EntryPointAccessors의 적절한 static 메서드를 사용해야 한다.
또한, 매개변수는 Component 인스턴스이거나 Component holder 역할을 하는 @AndroidEntryPoint 객체여야 함.
매개변수로 전달하는 Component와 EntryPointAccessors static 메서드가 모두 @EntryPoint interface의 @InstallIn 어노테이션에 있는 Android Class와 일치하는지 확인

class ExampleContentProvider: ContentProvider() {
    ...

  override fun query(...): Cursor {
    val appContext = context?.applicationContext ?: throw IllegalStateException()
    val hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint::class.java)

    val analyticsService = hiltEntryPoint.analyticsService()
    ...
  }
}

위 예는 EntryPoint가 SingletonComponent에 install되어 있으므로 ApplicationContext를 사용하여 entry point를 retrieve해야 한다.
만약, retrieve하려는 binding이 ActivityComponent에 있다면 ActivityContext를 대신 사용.


  • Hilt and Dagger

Hilt는 Dagger Dedenpency 라이브러리를 기반으로 빌드되어 Dagger를 Android 애플리케이션에 통합하는 표준 방법을 제공.

Dagger와 관련해 Hilt의 목표는 다음과 같다.

  1. Android 앱을 위한 Dagger 관련 infrastructure 간소화
  2. 앱 간의 setup, readability 및 code sharing을 용이하게 하기 위한 표준 Component 및 Scope 세트 생성
  3. 테스트, 디버그 또는 릴리즈와 같은 다양한 빌드 타입에 서로 다른 binding을 provision하는 쉬운 방법 제공

Android 운영체제는 많은 자체 프레임워크 클래스를 인스턴스화하므로 Android 앱에서 Dagger를 사용하려면 상당한 양의 boilerplate를 작성해야 한다. 이를 위해 Hilt는 Android 애플리케이션에서 Dagger 사용과 관련된 boilerplate code를 줄임.

  • Hilt는 자동으로 다음을 생성하고 제공함.
  1. 수동으로 생성해야 하는 Dagger와 Android 프레임워크 클래스를 통합하기 위한 Component
  2. Hilt가 자동으로 생성하는 Component와 함께 사용할 Scope annotations
  3. Application 또는 Activity와 같은 Android Class를 나타내는 미리 정의된 binding
  4. @ApplicationContext 및 @ActivityContext를 나타내는 미리 정의된 qualifires

Dagger 및 Hilt Code는 동일한 codebase에 공존할 수 있다.
그러나 대부분의 경우 Android에서 Dagger의 모든 사용을 관리하려면 Hilt를 사용하는 것이 가장 베스트


  • Hilt 동작방식
  • Application 내부에 LifeCycle을 따르는 Component라는 보관함을 만들고, 그 안에 RoomInstance, RetrofitInstance 같은 의존 객체를 생성한다.
  • Component안에는 하위 Component를 포함할 수 도 있다.
  • Activity 혹은 Fragment에서 의존객체 요청이 오면 해당 의존객체를 Component를 통해서 반환한다.
  • Hilt currently supports the following Android classes:
    Hilt는 다음 안드로이드 클래스에서만 의존성 주입이 가능하다.
  1. Application (by using @HiltAndroidApp)
  2. ViewModel (by using @HiltViewModel)
  3. (by using @AndroidEntryPoint)
    Activity
    Fragment
    View
    Service
    BroadcastReceiver

각 클래스별로 해당되는 어노테이션을 붙여주면 Hilt가 의존성을 보관하기 위한 Component라는 보관함을 만들고 Component 안에 생성된 의존객체는 다른 Component에 의존성을 주입할 수 있다.

  • 앱이 객체를 요청할 때마다 새로운 객체가 만들어지는 것을 방지하기 위해
    각 의존객체에는 Scope LifeCycle을 지정해줄 수 있다.

    만약 A라는 의존객체에 @Singleton 어노테이션을 붙이면 앱 전체에서 하나의 객체만 만들 수 있게 된다.
    만약 A라는 의존객체에 @ActivityScoped 어노테이션을 붙이면 해당 액티비티에서는 오직 하나의 의존객체만이 만들어질 수 있다.

  • Component 계층정책에 따라 하위 Component는 상위 scope를 가진 의존객체에 접근할 수 있다.

  • Component 내부에 의존객체를 만들었으면 이 의존객체를 주입 받을 객체를 연결하는 (=Binding) 과정을 수행하면 된다.

  • Hilt에서는 의존성을 제공할 객체와 주입받을 객체 양쪽에 @Inject 어노테이션을 붙이면서 바인딩이 이루어지는데, 이때, 의존성을 주입받을 객체가 Component를 가지고 있어야만 의존성을 주입받을 수 있기 때문에 주입받을 클래스에 @AndroidEntryPoint를 붙여줘서 Component를 만들어줘야 한다.

  • Hilt는 field, construtor 2가지 방법으로 의존성을 주입받을 수 있다.

      1. Field injection
// AnalyticsAdapter Instance를 ExampleActivity의 analytics field에 주입하기 위해서
// 주입받을 클래스에 @AndroidEntryPoint를 붙여 Component를 생성하고
// analytics field에 @Inject을 붙여 주입받을 Field임을 Hilt에게 알려준다.
class AnalyticsAdapter @Inject constructor() {
	...
}

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
	@Inject lateinit var analytics : AnalyticsAdapter
}
    1. Constructor injection
/*
주입받을 클래스에 @androidEntryPoint를 붙이고
생성자 쪽에 @Inject를 붙여 의존성을 주입하면 됨.
즉, Hilt에게 
AnalyticsAdapter라는 의존성을 제공할 객체의 정보를 알려주고
ExampleActivity에서는 자신의 Component에 AnalyticsAdapter를 의존성으로 주입받을 것이라는 것을 알려주는 것이다.
*/
class AnalyticsAdapter @Inject constructor() {
	...
}

@AndroidEntryPoint
class ExampleActivity @Inject constructor(
	var analytics : AnalyticsAdapter
) : AppCompatActivity() {
	...
}
  • Hilt에서는 의존 객체를 담는 클래스를 @Module 어노테이션을 붙여 정의함
  1. Component를 만들고
  2. Component 내부에 모듈을 @InstallIn 어노테이션을 통해 정의 및 설치하고
  3. Module 내부에 의존객체를 생성한 뒤
  4. 의존성 주입을 통해 모듈 내부의 의존객체를 다른 Component에 주입하는 구조이다.
  • 모듈 내부의 모든 의존객체는 다른 Component에 주입할 수 있지만, 단, interface 혹은 외부 라이브러리의 인스턴스는 Hilt가 주입할 수 없음.
    따라서, 이것들은 @Provides, @Binds 어노테이션을 이용해서 Hilt에게 정보를 알려주게 된다.

  • 외부 라이브러리 혹은 빌드패턴 객체를 주입받아야 될 경우
    @Provides 어노테이션을 이용한다.

// AnalyticsModule 클래스는 @Module 어노테이션을 통해 Hilt Module로 선언
// @InstallIn(ActivityComponent::class)를 통해
// AnalyticsModule이 ActivityComponent안에 설치됨.(scope는 Activity)
@Module 
@InstallIn(ActivityComponent.class)
object AnalyticsModule {

// Hilt 모듈 내부에 함수를 생성하고 이 함수에 @Provides 어노테이션을 지정하여
// Hilt에게 이 AnalyticsModule이 AnalyticsService 인스턴스의 의존성을 제공하는 방법을 알림.
// 즉, 외부에 이 AnalyticsService 의존성을 제공할 수 있게 됨.
  @Provides
  fun provideAnalyticsService(
  // 함수 매개변수는 Hilt에게 해당 타입의 dependencies들을 알려줌.
   '''
   Potential dependencies of this type 
   '''
  ) : AnalyticsService { // 함수 리턴타입은 Hilt에게 함수가 어떤 타입의 인스턴스를 제공하는지 알려줌.
  
  // 함수 body는 Hilt에게 해당 타입의 인스턴스를 제공하는 방법을 알려줌.
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}
  • interface를 의존객체로 제공하는 경우
    @Binds 어노테이션을 이용한다.
// If AnalyticsService is an interface.
interface AnalyticsService {
	fun analyticsMethods()
}

// AnalyticsService의 구현체인 AnalyticsServiceImpl 인스턴스를
// fun bindAnalyticsService 매개변수로 받고
// bindAnalyticsService함수는 interface인 AnalyticsService를 반환
// interface를 의존객체로 제공하는 것이므로 @Binds를 붙인다.
// 단 이때는 abstract로 만들어줘야 한다.
class AnalyticsServiceImpl @Inject constructor(
	...
) : AnalyticsService {
...
}

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

  @Singleton // @InstallIn(SingletonComponent::class)와 일치해야 함.
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}
profile
https://github.com/nohjunh

0개의 댓글