본문 바로가기

안드로이드/라이브러리 추천

[AOS] Dagger Hilt - DI

반응형

요즘 mvvm 검색하면 항상 붙어 나오는 키워드중 하나가 DI인것 같습니다. ( 요즘이 아니라 한참 오래전이긴 함 ) 

MVVM을 적용해보면서 코인은 써봤지만 hilt는 'Dagger'의 학습곡선이 상당하다는 얘기만 들어서 엄두도 못내다가 이직한 회사에 힐트가 적용되어 있어 공부한 내용을 정리해봅니닷 

 

| DI 필요성 ( 의존성 주입 )

A 클래스에서 B 클래스를 객체화 해서 사용하는 경우를 예를 들어보면 아래와 같습니다.

class A{
	val b = B()
    ...
}

class B{
	...
}

만약 B클래스의 기능 변화로 객체화시 넘겨줘야 하는 변수가 생긴다면 B클래스를 객체화 하는 클래스를 모두 찾아서 그에 맞게 변경 해줘야 합니다.  

class B constructor(val number: Int){
	....
}

class A {
	val b = B(5)
    ...

}

 

사용하고 있는 클래스가 A클래스 하나라면 큰 문제가 안되지만, 프로젝트 사이즈가 커질수록 사용하는 곳은 증가하고 유지보수 비용도 증가합니다. ( 흔하게 발생하는 문제 ) 

하지만 의존성 주입 라이브러리를 사용하면 보다 편리하게 관리할 수 있습니다. 

그외 장점

  • 코드 가독성 증가
  • Unit Test 용이
  • 코드 재활용성 증가

| 라이브러리 종류

대표적으로 koin과 hilt가 많이 쓰이는걸로 알고 있고 해당 포스팅은 hilt와 dagger 개념에 대해 다루도록 하겠습니다.

 

| Dagger 기본 개념

hilt는 dagger를 기반으로 만들어진 라이브러리다보니 가볍게 개념정도 훑고 가실게요 !

기본적인 Dagger개념을 알고 계신분은 중간 'Hilt' 부터 시작하시면 됩니다.

  • Inject
  • Component
  • Subcomponent
  • Module
  • Scope

Inject

의존성 주입을 요청하는 어노테이션입니다. 요청시 연결된 Component가 Module로부터 생성한 객체를 넘겨줍니다. 

필드, 생성자, 메소드에 붙일 수 있습니다.

 

Component

연결된 Module을 이용하여 의존성 객체를 생성하고 ,Inject로 요청받은 인스턴스에 객체를 주입합니다.

의존성을 요청받고 주입하는 Dagger에 핵심입니다.

Interface나 abstract 클래스에 붙일 수 있다 

 

Subcomponent

Component의 하위 계층으로 또 다른 Sub를 둘수 있습니다. Inject 요청시 제일 하위 Component부터 의존성을 검색하면서 없으면 올라갑니다.

 

Module

의존성 객체를 생성하고 Scope에 따라 관리합니다. Component와 연결되어 있습니다.

클래스에만 붙일 수 있습니다.

 

Scope

생성된 객체의 Lifecycle 범위입니다. Module에서 Scope에 따라 객체를 관리합니다.

 

| Dagger2 사용시 문제점 

안드로이드에서 Dagger2를 사용하려면 ApplicationComponent와 ActivityCoponent를 개별적으로 생성하고 각 코드 컴포넌트마다 연결을 도울 Builder를 정의하는 보일러 플레이트 코드를 아래 사진 처럼 작성해야 하는 불편함이 있습니다.

아래 예시 사진은 링크에서 발췌했습니다. ( 상세 기본 개념은 해당 블로그를 잘 정리되어 있으니 참고해주세요. 문제 있을시 삭제하도록 하겠습니다.) 

Aplication Component 정의 예시 코드

| Hilt 등장

기본 컴포넌트를 제공하기 때문에 위와 같이 각 Component를 정의하고 build 해줄 필요 없습니다. 

그 외에도 

  • 외부 객체 주입으로 코드 재사용성 증가 및 유지 보수성 증가 
  • 테스트 용이
  • 앱 생명주기에 따라 객체 자동 주입 및 삭제
  • 보일러 플레이트 코드 감소 
  • 기존 Dagger보다 사용하기 쉬움 

기본 제공 Component

그리고 각 Componet별 Scope, 생성 및 파괴 시점은 아래와 같습니다. 

 

| Hilt 사용하기

build.gradle ( project level ) 

buildscript {
    ...
    dependencies {
        ...
        classpath("com.google.dagger:hilt-android-gradle-plugin:2.38.1")
    }
}

build.gradle ( app level ) 

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

android {
    ...
}

dependencies {
    implementation("com.google.dagger:hilt-android:2.38.1")
    kapt("com.google.dagger:hilt-android-compiler:2.38.1")
    
    implementation 'androidx.activity:activity-ktx:1.2.0'
    implementation 'androidx.fragment:fragment-ktx:1.3.0'
}

 

Application Class

Hilt는 Application Class와 Application Context를 사용해 많은 일을 하기 때문에 Applicaion 클래스가 필요합니다. 

'HiltAndroidApp' 어노테이션을 Application 파일에 지정해야 합니다.

Hilt는 Application 생명 주기를 따르며 해당 어노테이션으로 의존성 주입의 시작점을 지정합니다.

@HiltAndroidApp
class BaseApplication : Application()

 

AndroidEntryPoint

Application Component 하위에 해당하는 클래스들에 붙여주는 어노테이션입니다.

  • Activity  ( ComponentActivitiy를 상속받는 Activity일 경우, 그 예로 AppCompatActivity ) 
  • Fragment ( androidx.Fragment 상속받는 클래스에만 가능 ) 
  • View
  • Service
  • BroadcastReceiver
@AndroidEntryPoint
class SplashActivity: AppCompatActivity()

 

Inject

Injection은 크게 Field Injection과 Constructor Injection으로 나누어 볼 수 있는데 

Field Injection

아래 코드는 ItemAdapter가 Hilt에 의해 주입될 수 있는 객체라는걸 알려주기 위해 생성자에 '@Inject' 어노테이션을 붙였습니다.

class ItemAdapter @Inject constructor(){
	...
}

위 클래스는 Component 들에서 사용이 가능합니다 ( @AndroidEntryPoint가 붙은 Activity등 ) 

 

아래 코드와 같이 의존성 주입을 받아서 사용한 변수에 '@Inject' 어노테이션을 붙여서 사용합니다. 그러면 객체화 하지 않아도 Hilt로부터 주입된 객체를 사용할 수 있습니다. ( 단 private 변수는 주입 받을 수 없습니다. ) 

class MainActivity: AppCompatActivity(){
	@Inject lateinit var adapter: ItemAdapter
    ...
}

 

Constructor Injection

아래 코드는 mvvm의 뷰모델과 useCase의 예제입니다.

'TestUseCase' constructor에 @Inject 어노테이션이 붙어서 hilt에 의해 주입될 객체라고 표시되어 있습니다.

'ViewModel' 도 동일하지만 생성자에 'TestUseCase'를 주입받고 있는데 이를 Constructor Injection이라 하며, 이로 인해 생성시 어떤 클래스의 객체가 필요한지 개발자도 알고 hilt도 알 수 있습니다.

@HiltViewModel
class ViewModel
	@Inject constructor(
    private val testUserCase: TestUseCase
): ViewModel() {
	....
}
	....

class TestUseCase
	@Inject constructor(){
    ....
}

 

Module 과 Bind, Provides

Module

위 두가지의 Injection을 사용해서 주입할 수 없는 경우가 2가지 있는데 그럴 경우 Module을 이용해 의존성을 주입하는 방법을 정의해줍니다.  

주입할 수 없는 경우 

- Interface는 Constructor Injection 를 통해 주입할 수 없습니다.  -> bind 사용 

- 외부 라이브러리 클래스의 경우 ( ex. Retrofit )  -> Provider 사용 

위의 경우들의 의존성을 주입하는 메소드들을 정의해놓은 클래스를 모듈이라고 생각하시면 됩니다. 

 

bind

아래처럼 repository의 정의와 구현이 나뉘어져 있을 경우 의존성 주입시 에러가 발생합니다.

interface Repository{
	fun getName(): String
}

class RepositoryImpl @Inject constructor(): Repository{
	overide fun getName(): String{
    	return "wony"
    }
}

class A
	@Inject constructor(private val repo: Repository){
    ..    
}

그렇기 때문에 아래처럼 모듈을 생성해서 Repository를 주입받고 싶을 경우 어떻게 넣어줄지 정의합니다.

@Module
@InstallIn(ActivityComponent::Class)
abstract class RepoModule{
	@Bind
    abstract fun bindRepository(repo: RepositoryImpl): Repository

}

클래스 상단에 'Module' 어노테이션과 'InstallIn' 어노테이션을 붙여주는데 여기서 InstallIn은 해당 모듈이 어떤 scope에 사용할지 정의하는 것입니다.  그리고 메소드를 보시면 파라미터엔 구현 클래스를 return 타입은 인터페이스로 정의된걸 알 수 있습니다.

이처럼 인터페이스와 같이 객체 생성을 어떻게 할지 모를때 bind를 사용하시면 됩니다.

 

Provides

아래 코드처럼 사용하는데 Room의 경우에도 Provides를 사용합니다.

@Module
@InstallIn(ActivityComponent::class)
object AppModule {
	@Provides
    fun provideRetrofit(): RetrofitService{
    	return Retrofit.Builder()
        	.baseUrl("http")
            .build()
            .create(Service::class.java)
    }
}

 

ViewModel ( AAC ) 

Hilt에서 ViewModel에 쉽게 Inject하도록 기능을 제공합니다.

implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'

먼저 라이브러리를 추가하고 본 포스팅에서 예제로 ViewModel을 사용했었는데, 아래와 같이 @HiltViewModel 어노테이션만 

붙여주면 됩니다.

@HiltViewModel
class ViewModel
	@Inject constructor(
    private val testUserCase: TestUseCase
): ViewModel() {
	....
}

Activity에서 사용시엔 아래처럼 사용하면 됩니다.

@AndroidEntryPoint
class MainActivity: AppCompatActivity(){
	private val vm: ViewModel by viewModels()
}

 

위의 정보들이 많다면 많지만 모든 정보는 아닙니다. 자세한 사항은 공식 문서를 참고해주시고, 이해가 잘 안가시는 부분은 여러번 읽거나 다른 사람들의 블로그와 함께 참고 하시는걸 추천드립니다. ( 저두 엄청 보고 적용해가면서 익혔.. ) 

 

 

 

 

 

 

반응형