[Android] Hilt 사용하기

Hanbin·2022년 3월 16일
5

Android

목록 보기
6/6
post-thumbnail

💡 DI(Dependency Injection)

DI는 프로그래밍에 널리 사용되는 방식으로 객체의 생성과 사용의 관심을 분리하는 것이다. 다시 말하면 클라이언트에서 어떤 서비스를 이용할 때 이를 외부에서 생성하여 해당 서비스를 사용할 수 있게 해주는 것이다. 클라이언트는 서비스의 생성 및 구성 방식에 대해 알 필요가 없이 서비스의 기능을 사용하면 된다.

DI를 사용했을 때의 장점은 뭐가 있을까?

  • 클라이언트와 서비스가 분리되어 있으므로, 클라이언트를 테스트할 때 모의객체를 사용하여 더 쉽게 테스트할 수 있다.
  • 각각이 독립되어 있기 때문에 결합도를 낮추고 유지보수에 용이하다.
  • Boilerplate code를 줄일 수 있다.
  • 코드의 재사용성이 증가한다.

🧪 Hilt

Hilt는 Android 클래스에 의존성 주입을 지원하고 수명 주기를 자동으로 관리해 주기 때문에 Android에서 DI를 사용하기에 적합한 라이브러리이다.

Hilt 공식문서 정리

프로젝트 수준의 build.gradle에 추가해 준다.

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

앱수준의 build.gradle에 추가해 준다.

...
plugins {
  id 'kotlin-kapt'
  id 'dagger.hilt.android.plugin'
}

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

dependencies {
    implementation "com.google.dagger:hilt-android:2.38.1"
    kapt "com.google.dagger:hilt-compiler:2.38.1"
}

Application 클래스 만들어 준다.

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

Android 클래스에 종속성 주입.

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

Activity 외에도 Android 클래스를 지원한다.

  • Application ( @HiltAndroidApp )
  • ViewModel ( @HiltViewModel )
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

이와 같이 Android 클래스에 주석을 추가하는 경우 해당 클래스에 종속된 Android 클래스에도 주석을 달아야 한다. 예를 들어 Activity에 종속된 Fragment의 경우이다.

의존성 주입 정의

의존성을 주입하기 위해선 사용되는 항목의 인스턴스 제공 방법을 알아야 한다.

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

@Inject 주석을 사용하여 인스턴스를 제공하는 방법을 Hilt에 알려준다.

Hilt 모듈

위와 같은 방법으로 진행할 수 없는 경우가 있다. 예를 들어 인터페이스를 생성자에 삽입, 외부 라이브러리의 클래스와 같이 소유하지 않은 경우 불가하다. 이럴 때 Hilt 모듈을 사용하여 해결할 수 있다.

@Module주석과 @InstallIn 주석을 사용한다.

@Binds를 사용하여 인터페이스 인스턴스 삽입

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

@InstallIn(ActivityComponent::class) 주석은 해당 모듈이 모든 Activity에서 사용할 수 있음을 의미한다.

@Provides를 사용하여 인스턴스 삽입

소유하지 않은 클래스의 경우( 외부 라이브러리 Room, Retrofit 등), 빌더 패턴으로 인스턴스를 생성해야 하는 경우 사용된다. @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)
  }
}

Hilt에 정의된 한정자

Application이나 Activity의 Contect가 필요할 수 있으므로 @ApplicationContext@ActivityContext 한정자를 제공한다. 아래와 같이 사용할 수 있다.

class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context,
    private val service: AnalyticsService
) { ... }

Android 클래스용으로 생성된 Component

Android 클래스마다 @InstallIn 주석에 참조할 수 있는 Hilt Component가 있다. 이전에 봤던 ActivityComponent이 그 예이다.

Component lifetimes
구성요소의 생명주기에 따라 인스턴스를 자동으로 만들고 제거한다.

Component scopes
이전 예제에서의 AnalyticsAdapter의 경우 별다른 scope를 설정해 주지 않았기 때문에 AnalyticsAdapter의 인스턴스를 제공할 때마다 새 인스턴스를 제공한다. 이때 scope를 지정하여 특정 Component의 생명주기 동안에 동일한 인스턴스를 제공할 수 있다.

아래와 같이 @ActivityScoped 주석을 사용하면 해당 Activity의 생명주기 동안 동일한 인스턴스를 제공하게 된다.

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

🙏 마치며

이번에는 DI란 무엇이고 왜 필요한지, 안드로이드에서 DI를 사용할 때 효과적인 Hilt의 사용법에 대해 간단하게 알아보았다. 다음 포스팅에서는 이번에 배운 Hilt를 사용하여 간단한 앱을 만들어 보려고 한다.

📜 참고

의존성 주입(위키피디아)
힐트 공식문서

0개의 댓글