코린이 개발로그
DI 와 Hilt 와 Repository패턴 본문
DI란?
DI는 Dependency Injection의 줄임말로 의존성 주입이라는 뜻을 가지고 있습니다.
일반적으로 클래스에는 다른 클래스 참조가 필요한 상황이 자주 있습니다. 예를 들면 Car라는 클래스는 Engine 클래스 참조가 필요할 수 있습니다. 이처럼 필요한 클래스를 종속 항목이라고 하며, 이 예에서 Car 클래스가 실행되기 위해서는 Engine 클래스의 인스턴스가 있어야 합니다.
이제 여기서 우리는 Engine클래스를 만들어 주는데 3가지 방법이 있습니다.
- Car 라는 클래스 안에서 직접 Engine 인스턴스를 생성한다.
- 다른 곳에서 해당 인스턴스를 불러온다.
- 매개변수로 Engine 인스턴스를 제공받는다.
이 세번째 방법이 바로 의존성 주입입니다. 이로써 Car클래스는 Engine인스턴스를 생성하는 책임에서 벗어날 수 있습니다.
의존성 주입을 하는 이유는 여러 가지가 있습니다.
- 코드 재사용 가능
- 리팩터링 편의성
- 테스트 편의성
- 보일러코드를 줄일 수 있다
Hilt란?
Hilt는 Android에서 종속 항목 삽입을 위한 Jetpack의 권장 라이브러리입니다. Hilt는 프로젝트의 모든 Android 클래스에 컨테이너를 제공하고 수명 주기를 자동으로 관리함으로써 애플리케이션에서 DI를 실행하는 표준 방법을 정의합니다.
Hilt는 Dagger가 제공하는 컴파일 타임 정확성, 런타임 성능, 확장성 및 Android 스튜디오 지원의 이점을 누리기 위해 인기 있는 DI 라이브러리 Dagger를 기반으로 빌드되었습니다.
Hilt를 사용해보자
Hilt를 사용하기 위해서는 제일 먼저 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를 사용하고 있다는 것을 알리기 위해 Application에 어노테이션을 달아주어야 합니다.
@HiltAndroidApp
class ExampleApplication : Application() { ... }
여기까지 하면 일단 기본 준비는 끝났습니다. 이제 원하는 클래스를 Hilt에 등록하여 원하는 클래스에 주입해주면 됩니다.
우선 Hilt의 의존성 주입에는 아래 클래스들에서만 지원을 하고 있습니다.
- Application(@HiltAndroidApp을 사용하여)
- Activity
- Fragment
- View
- Service
- BroadcastReceiver
예를 들어 자기가 직접 만든 클래스나 이외의 클래스들에 Hilt를 사용할 수 없습니다 (위의 클래스들을 상속받지 않는다면).
만약 위의 클래스에 해당한다면
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
@Inject lateinit var analytics: AnalyticsAdapter
...
}
이런 식으로 원하는 의존성을 주입할 수 있습니다.
자 이제 주입해줄 클래스들을 생성하여야 합니다.
이 클래스들은 각자 어노테이션의 종류에 따라 생명주기를 부여받습니다.
생성된 구성요소 | 생성 위치 | 제거 위치 |
ApplicationComponent | Application#onCreate() | Application#onDestroy() |
ActivityRetainedComponent | Activity#onCreate() | Activity#onDestroy() |
ActivityComponent | Activity#onCreate() | Activity#onDestroy() |
FragmentComponent | Fragment#onAttach() | Fragment#onDestroy() |
ViewComponent | View#super() | 제거된 뷰 |
ViewWithFragmentComponent | View#super() | 제거된 뷰 |
ServiceComponent | Service#onCreate() | Service#onDestroy() |
예를 들어 애플리케이션과 같은 생명주기를 가지고 있어야 할 의존성 클래스를 생성하여야 한다면
@Module
@InstallIn(ApplicationComponent::class)
class A {
......
}
이런 식으로 만들어 줄 수 있습니다.
@Binds
일반적인 클래스는 위와 같이 의존성을 주입해 줄 수 있지만 Interface는 바로 의존성을 Instance가 없기 때문에 직접 넣어줄 수 없습니다. 이럴 때는 @Binds 어노테이션으로 해결할 수 있습니다.
@InstallIn(SingletonComponent::class)
@Module
interface RepositoryModule {
@Binds
fun provideRepositoryImpl(repository: RepositoryImpl): Repository
}
이로써 Repository의 의존성을 주입하게 되면 자연스럽게 RepositoryImpl이 주입되게 됩니다.
만약 하나의 인터페이스가 여러 개의 구현이 있다면
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class RemoteRepository
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class LocalRepository
이런 식으로 @Qualifier를 정의하여야 합니다. 그 후에
@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {
@RemoteRepository
@Binds
fun provideRemoteRepository(
repository: RemoteRepository
): Repository
@LocalRepository
@Binds
fun provideLocalRepository(
repository: LocalRepository
): Repository
}
@AndroidEntryPoint
class ExampleActivity: AppCompatActivity() {
@RemoteRepository
@Inject lateinit var repository: Repository
}
이런식으로 구분해줄 수 있습니다.
@Provides
이 어노테이션은 인터페이스 말고도 클래스가 외부 라이브러리에서 제공되어 클래스를 소유하지 않을 경우 (Retrofit, Room 등) 또는 Builder패턴으로 인스턴스를 생성하여야 하는 경우 사용됩니다.
@Module
@InstallIn(ActivityComponent::class)
object DatabaseModule {
@Provides
@Singleton
fun provideContactDataBase(@ApplicationContext appContext: Context): MyDatabase {
return Room.databaseBuilder(
appContext,
MyDatabase::class.java,
"my.db"
).build()
}
}
이런 식으로 RoomDB 클래스에 접근할 수 있습니다.
사실 두 어노테이션이 많이 헷갈릴 수도 있습니다. 둘의 차이점이라면 @Binds는 객체를 직접 만들어 주지 않아도 될 때, @Provides는 객체를 직접만들어 줘야 할 때 사용한다고 생각하면 될 거 같습니다.
위를 예시로 들면 RepositoryImpl은 이미 Hilt에 등록되어 있기 때문에 직접 생성을 하지 않아도 주입이 되지만, 아래의 Room의 생성자 같은 경우 직접 만들어 줘야 하기 때문에 @Provides를 사용한다고 생각하시면 됩니다.
'Kotlin & Android' 카테고리의 다른 글
Compose ViewModel (0) | 2025.02.26 |
---|---|
Android ListView Widget을 만들어보자 (0) | 2021.11.29 |
AAC 와 MVVM (0) | 2021.11.09 |
FireBase Gradle build 오류 (2) | 2021.04.24 |