안드로이드 의존성 주입과 Hilt (2)

Hilt의 주요 Annotaion

1>HiltAndroidApp

 

기존의 컴포넌트 생성은 아래의 코드와 같이 onCreate()에서 컴포넌트를 인스턴스화 하였다.

hiltAndroidApp 어노테이션만 추가함으로써 컴포넌트를 인스턴스화 할 수 있다.

이 때 의존성 주입은 super.onCreate()에서 이루어진다.

바이트코드 변환 때문에 onCreate에서 컴포넌트 인스턴스 생성이 가능해진다.

HiltAndroidApp은 Hilt 코드 생성을 시작하고 반드시 Application 클래스에 추가해야 한다.

 

[ByteCode Transformation]

바이트 코드 변환을 통해서 MemoApplication 클래스는 Hilt_MemoApplication을 상속받을 필요 없이,

Application() 상속만으로 컴포넌트를 인스턴스화 할 수 있다.

만약 Gradle 플러그인을 사용하지 않는다면, 아래와 같이 이름을 명시해야 한다.

2>AndroidEntryPoint

어노테이션이 추가된 안드로이드 클래스에 DI 컨테이너를 추가, @HiltAndroidApp의 설정 후 사용이 가능하다.

HiltAndroidApp은 Component를 생성, AndroidEntryPoint는 SubComponent를 생성한다. 

 

컨텐트 프로바이더를 제외한 activity, fragment, view, service, broadcastreceiver를 지원한다. 

컨텐트 프로바이더의 경우 까다로운 생명 주기를 가지기 때문에 지원하지 않는다.

 

[Hilt Component의 특징]

- Dagger와 다르게 직접적으로 인스턴스화 할 필요가 없다.

- 각 생명 주기와 기능에 맞는 표준화된 컴포넌트 세트와 스코프를 제공한다.

- 컴포넌트들은 계층으로 이루어져 있으며, 하위 컴포넌트는 상위 컴포넌트의 의존성에 접근할 수 있다.

 

[Hilt의 Scope]

Hilt의 Scope 어노테이션이 없는 경우 

 

[Hilt의 Scope 어노테이션 필요성 이해]

 

애플리케이션 컴포넌트로부터 주입받은 메모 액티비티 내부의 메모 래퍼지토리 인스턴스가 있다고 가정한다.

만약 다른 액티비티 인스턴스에서도 동일한 메모 래퍼지토리를 요청할 경우 서로 다른 래퍼지토리를 가지게 된다.

[Hilt의 Scope 어노테이션 활용]

여기서 주의할 점은 모듈에서 사용하는 스코프 어노테이션은 InstallIn 에 명시 된 컴포넌트와 쌍을 이루는 스코프를 사용해야한다.

애플리케이션 컴포넌트에 설치할 때 액티비티나 프래그먼트 스코프를 사용하면 안된다는 것이다.

각 컴포넌트에 맞는 스코프를 적용해야 의존성 주입을 활용할 수 있다.

이제 두 액티비티가 같은 메모리래퍼지토리 인스턴스를 주입받게 된다.

Hilt의 장점인 쉬운 자원 공유가 적용되는 것이다.

 

 

대표 컴포넌트 바인딩

 

Hilt Module  [InstallIn]

3>InstallIn

Hilt가 생성하는 DI 컨테이너에 어떤 모듈을 사용할 지 명시한다.

컴파일 타임에 Hilt에서 InstallIn 어노테이션을 확인하고 관련 코드를 생성한다.

올바르지 않은 컴포넌트 스코프를 사용할 경우 컴파일 에러가 발생할 수 있다.

InstallIn을 사용해서 컴포넌트를 명시했으므로, 관련 컴포넌트로 Hilt가 자동으로 매칭한다.

만약 FragmentComponent에서도 MyModule의 기능이 필요할 경우,

상위 컴포넌트인 ActivityComponent의 모듈을 설치하면 된다.

하위 컴포넌트가 상위 컴포넌트의 의존성에 엑세스할 수 있다. 

Hilt는 @Module 클래스에 @InstallIn이 없으면 컴파일 에러가 발생한다. 

모듈이 어떤 컴포넌트에 설치되었는지 명확하게 하기 위해서 해당 기능을 부여한다.

 

4>EntryPoint

- Hilt가 지원하지 않는 클래스에서 의존성이 필요한 경우 사용한다.

- ContentProvider, DFM, Dagger를 사용하지 않는 3rd-party 라이브러리 등에서 사용한다.

- @EntryPoint는 인터페이스에서만 사용된다.

- @InstallIn이 반드시 함께 사용되어야 한다,

- EntryPoints 클래스의 정적 메서드를 통해 그래프에 접근해야 한다.

 

 

@EntryPoint 사용 예제 

[엔트리 포인트로 ContentProvider를 사용하는 경우]

@InstallIn 어노테이선으로는 ContentProvider를 사용할 수 없으므로 @EntryPoint 어노테이션을 추가한다.

아래 코드에서 EntryPointAccessors는 엔트리 서포트 클래스를 감싼 유틸성 클래스로,

엔트리 포인트를 가져오는 값과 동일하다.

[DFM을 사용하는 경우]

프로젝트를 DFM으로 구성하는 경우 기존 앱 모듈에 의존해야 한다.

이 경우 Dynamic Feature Module을 참조할 수 없기 때문에 컴파일 타임에 서브 컴포넌트 구조로 그래프를 생성할 수 없게 된다.

하지만 컴포넌트를 상속하는 구조인 dependencies 기능을 사용하면 DFM에서 애플리케이션 컴포넌트를 상속받는 컴포넌트를 생성할 수 있다.

onCreate에서 사용

 

그 외의 Hilt에 대한 내용

[AndroidX Extension]

Hilt는 Jetpack 라이브러리 클래스를 위해 extension을 제공한다. (확장 라이브러리)

ViewModel과 WorkManager 제공

 

ViewModel을 사용할 경우, 아래와 같이 savedStateHandle를 가져오는 핸들을 통해서 동적인 주입이 가능하다.

WorkManager에 Hilt를 주입하는 경우, 아래와 같이 사용한다. 

뷰모델과 동일한 방식으로 동작하나, WorkInject 어노테이션으로 적용해야 한다.

WorkManager에 대한 정의가 끝났다면, Application 클래스에 Configuration.Provider 인터페이스를 구현해야 한다.

그 후 Hilt가 제공하는 HiltWorkerFactory를 주입받아, WorkManager 설정을 완료한다.

 

[Custom Component]

Hilt는 표준화 된 컴포넌트를 가지게 된다.

그 이외의 컴포넌트를 생성하는 경우를 커스텀 컴포넌트라고 한다.

하지만 복잡하고 이해가 어려워지기 때문에 필요한 경우에만 사용하기를 권장한다.

- @DefineComponent

- @DefineComponent.Builder

- 반드시 애플리케이션 컴포넌트의 하위 계층 컴포넌트로 만들어야 한다.

- 표준 컴포넌트 계층 사이에 추가할 수 없다.

 

[Hilt의 설계 철학]

 

Monolithic components 특징 (단일 컴포넌트)

Single binding key space

-> 특정 바인딩이 어디로부터 왔는지 탐색이 쉬워지며, 코드량이 크게 감소한다.

-> 모듈이 설치될 수 있는 곳을 줄이기 때문에, 설정 및 테스트가 가능해진다.

-> 모든 서브 컴포넌트에 대한 반복적인 코드를 감소시킨다. (많은 액티비티+프래그먼트 사용 시 매우 큰 반복)