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

Hilt의 주요 Annotaion

1>HiltAndroidApp

 

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

스크린샷 2023-06-24 오후 9.33.48.png

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

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

스크린샷 2023-06-24 오후 9.35.09.png

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

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

 

[ByteCode Transformation]

스크린샷 2023-06-24 오후 9.38.10.png

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

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

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

스크린샷 2023-06-24 오후 9.40.27.png

2>AndroidEntryPoint

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

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

 

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

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

 

[Hilt Component의 특징]

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

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

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

 

[Hilt의 Scope]

스크린샷 2023-06-24 오후 9.46.46.png

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

스크린샷 2023-06-24 오후 9.48.00.png

 

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

 

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

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

스크린샷 2023-06-24 오후 9.48.58.png

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

스크린샷 2023-06-24 오후 11.25.43.png

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

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

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

스크린샷 2023-06-24 오후 11.28.41.png

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

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

 

 

대표 컴포넌트 바인딩

스크린샷 2023-06-24 오후 11.30.13.png

 

Hilt Module  [InstallIn]

3>InstallIn

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

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

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

스크린샷 2023-06-24 오후 11.35.14.png

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

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

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

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

스크린샷 2023-06-24 오후 11.38.07.png

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

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

 

4>EntryPoint

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

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

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

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

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

스크린샷 2023-06-24 오후 11.50.57.png
스크린샷 2023-06-24 오후 11.51.25.png

 

 

@EntryPoint 사용 예제 

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

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

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

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

스크린샷 2023-06-25 오전 12.10.23.png

[DFM을 사용하는 경우]

스크린샷 2023-06-25 오전 12.13.20.png

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

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

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

스크린샷 2023-06-25 오전 12.17.04.png
스크린샷 2023-06-25 오전 12.18.17.png
onCreate에서 사용

 

그 외의 Hilt에 대한 내용

[AndroidX Extension]

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

ViewModel과 WorkManager 제공

 

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

스크린샷 2023-06-25 오전 12.21.23.png

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

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

스크린샷 2023-06-25 오전 12.23.12.png

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

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

스크린샷 2023-06-25 오전 12.25.22.png

 

[Custom Component]

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

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

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

- @DefineComponent

- @DefineComponent.Builder

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

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

 

[Hilt의 설계 철학]

 

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

Single binding key space

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

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

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