[Android] Dagger Hilt 집중탐구 - 2

김준영·2024년 4월 1일
0

Android

목록 보기
7/17
post-thumbnail

이전글

[Android] Dagger Hilt 집중탐구 - 1

지난글에 이어서 이번엔 Hilt를 적용해보고 Hilt에 대한 개념을 좀 더 자세히 알아보려합니다!

Hilt를 쓸 준비

플러그인 (프로젝트 루트)

plugins {
  ...
  id("com.google.dagger.hilt.android") version "2.44" apply false
}

build.gradle (app 수준)

plugins {
  kotlin("kapt")
  id("com.google.dagger.hilt.android")
}

android {
  ...
}

dependencies {
  implementation("com.google.dagger:hilt-android:2.44")
  kapt("com.google.dagger:hilt-android-compiler:2.44")
}

// Allow references to generated code
kapt {
  correctErrorTypes = true
}

참고: Hilt는 자바 8기능을 사용합니다!!

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

💁🏻‍♂️ Hilt의 구성요소 계층 구조

Hilt를 접하신분들은 정말 많이 보셨을 그림입니다!
먼저 이 계층구조를 파악한 뒤에 이어나가겠습니다

구성요소에 모듈을 설치하면 구성요소의 다른 결합 또는 구성요소 계층 구조에서 그 아래에 있는 하위 구성요소의 다른 결합의 종속항목으로 설치된 모듈의 결합에 엑세스 할 수 있습니다.
즉 그림의 흐름대로 위에서 아래로 접근이 가능하다는 것이죠!

구성요소 기본 결합 🧐

그림에 적혀있는 주석에 대해 좀 더 자세히 알아볼까요?

여기서 구성요소란 무엇일까요? 구성요소는 종속항목(의존성)을 주입하는데 사용되는 부분을 말합니다 예를 들어 앱의 화면을 구성하는 활동(activity)나 프래그먼트가 구성요소가 될 수 있습니다!

즉, Hilt는 앱의 화면을 구성하는 각각의 활동이나 프래그먼트에 종속항목을 주입 할 때, 그 화면의 특성에 맞게 설정을 조절해 사용합니다

Hilt 애플리케이션 클래스

Hilt를 사용하는 모든 앱은 @HiltAndroidApp으로 주석이 지정된 Application 클래스를 포함해야합니다.
@HiltAndroidApp은 애플리케이션 수준 종속 항목 컨테이너 역할을 하는 애플리케이션의 기본 클래스를 비롯해 Hilt의 코드생성을 트리거합니다
즉 Hilt의 시작을 알리는 코드라고 이해하면 될 것 같습니다.

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

생성된 이 구성요소는 애플리케이션 객체의 수명주기에 연결되어 이와 관련한 종속 항목을 제공합니다. 또한 이는 앱의 상위 구성요소이므로 다른 구성요소는 이 상위 구성요소에서 제공하는 종속 항목에 엑세스 할 수 없습니다.

Android 클래스에 종속 항목 삽입

Application 클래스에 Hilt를 설정하고 애플리케이션 수준 구성요소를 사용할 수 있게되면 Hilt는 @AndroidEntryPoint 주석이 있는 다른 Android 클래스에 종속 항목을 제공할 수 있습니다.

@AndroidEntryPoint
class A : AppCompatActivity() { ... }

Hilt는 현재 Application, ViewModel(@HiltViewModel을 사용), Activity, Fragment, View, Service, BroadcastReceiver를 지원합니다.
만약 Android 클래스에 @AndroidEntryPoint로 주석을 지정하면 이 클래스에 종속된 Android 클래스에도 주석을 지정해야 합니다. (주석을 단 상위클래스를 주입받는 하위클래스도 주석을 달아줘야한다는 의미입니다!)

상위계층에서 종속받기 (주입받기)

@AndroidEntryPoint
class B : AppCompatActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

만약 클래스 A에 analyics라는 종속항목을 가져온다면 @Inject 주석을 사용해 필드 삽입을 실행합니다. Hilt가 이 객체를 효율적으로 관리합니다. 정말 편하죠?
만약 B라는 클래스가 최초로 주입을 받고, 이후 C라는 클래스가 동일한 객체를 주입받는다면 이미 생성된 객체를 반환해줍니다. 자원관리 측면에서도 장점이 느껴지시나요?

Hilt의 결합 정의

위 내용처럼 필드 삽입을 실행하려면 Hilt가 해당 구성요소에서 필요한 종속 항목의 인스턴스를 제공하는 방법을 알아야합니다.

결합에는 특정유형의 인스턴스를 종속항목으로 제공하는 데 필요한 정보가 포함됩니다
Hilt에게 결합정보를 알려주는 방법으론 생성자 삽입입니다.

class BClass @Inject constructor(
  private val service: AClass
) { ... }

이런식으로 지정된 클래스 생성자의 매개변수는 그 클래스의 종속 항목입니다.
이 코드의 경우 B 클래스에는 A 클래스가 종속항목으로 되어있습니다.
따라서 Hilt는 A 클래스의 인스턴스를 제공하는 방법도 알아야합니다

Hilt 모듈

때로는 생성자 삽입을 할 수 없는 상황도 있을겁니다

예를들어 인터페이스는 생성자 삽입을 할 수 없고 외부라이브러리 클래스와 같이 소유하지 않은 것들도 생성자 삽입을 할 수 없습니다

이럴땐 Hilt 모듈을 사용해 Hilt에 결합정보를 제공해줄수 있습니다
Hilt 모듈은 @Module로 주석이 지정된 클래스입니다.
이 모듈은 특정 유형의 인스턴스를 제공하는 방법을 Hilt에게 알려줍니다.
또한 @InstallIn 주석을 지정해 각 모듈을 사용하거나 설치할 안드로이드 클래스를 Hilt에게 알려야합니다

@Bind를 사용해 인터페이스 인스턴스 삽입

@Bind 주석은 인터페이스의 인스턴스를 제공해야 할 때 사용할 구현을 Hilt에게 알려줍니다.
주석이 지정된 함수는 Hilt에 다음 정보를 제공합니다

  1. 함수 반환 유형은 어떤 인터페이스의 인스턴스를 제공하는지 Hilt에게 알려줍니다
  2. 함수 매개변수는 제공할 구현을 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
}

@InstallIn(ActivityComponent::class) 주석의미는 AnalyticsModule의 모든 종속항목을 앱의 모든 activity에서 사용할 수 있음을 의미합니다

@Provides를 사용해 인스턴스 삽입

만약 외부라이브러리에서 제공되어 개발자가 소유하지 않은 경우는 어떡할까요?
HTTP 통신에 가장 흔히 쓰이는 Retrofit의 경우는 어떻게 해야할까요?

바로 @Provides 주석을 이용해 Hilt에게 알리는 방법이 있습니다.
이 주석은 다음 정보를 제공해줍니다.

  1. 함수 변환 유형은 함수가 어떤 유형의 인스턴스를 제공하는지 Hilt에게 알려줍니다
  2. 함수 매개변수는 해당 유형의 종속 항목을 Hilt에게 알려줍니다
@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)
  }
}

정리

Hilt의 목표는 무엇일까요?

  1. 안드로이드 앱을 위한 Dagger 관련 인프라 간소화
  2. 앱 간의 설정, 가독성 및 코드 공유를 용이하게 하기 위한 표준 구성요소 및 범위 생성
  3. 테스트, 디버그 또는 출시와 같은 다양한 빌드유형에 서로 다른 결합을 프로비저닝하는 쉬운 방법을 제공

이외에도 다양한 장점들은 직접 사용해보면서 느끼실 수 있을 것 같습니다!

회고

Hilt를 처음 접했을땐 이게 무엇인지도 모르고 그냥 클린아키텍처에 필요한 라이브러리구나 하고 생각했습니다 (그땐 프로젝트 끝내기 급급했습니다ㅠㅠ)
하지만 Hilt를 다시 처음부터 공식문서를 통해 천천히 배우고나니 왜 사용해야하는지, 또 제가 적용한 프로젝트에 Hilt를 적절히 쓴게 맞는지 깊게 생각해볼 수 있었던 시간을 가진 것 같습니다

참고문헌

Android 공식문서

profile
Android, Flutter를 공부하고 있습니다🧐

0개의 댓글

관련 채용 정보