▩ 목 차 ▩
1. Hilt 란?
1-1. 의존성 주입(Dependency Injection) 이란?
1-1-1. 원초적으로 의존성이 뭔데 ?
1-1-2. 그러면 의존 관계를 줄일 순 없나?(feat. 인터페이스)
1-1-3. 원초적으로 주입은 뭔데 ?
1-1-4. 의존성 주입의 영향은 뭔데?
1-2. Hilt란 ?
1-2-1. Hilt의 특징
1-2-2. Hilt 사용법
1-2-2-1. @HiltAndroidApp
1-2-2-2. @AndroidEntryPoint
1-2-2-3. @Inject
1-2-2-4. @EntryPoint
1-2-3. Hilt의 @Provides vs @Binds
2. Hilt 적용된 앱 해석하기(feat, MovieApp)
하.. Clean Architecture를 공부를 하면 100의 100인 사람들이 무슨 di 패키지를 만들고 그 폴더 안에서는 @Modul로 함께 정의 되어있었다.
나는 처음에는 Clean Architecture를 공부를 하였지만 이것을 이해하기 위해선 Hilt가 무엇이고 어떻게 사용이 되는지부터 알아야 한 것이였다..
후.. 자 드가자.. 일단 적용은 추후에 프로젝트를 진행하면서 하니 다른 사람들 프로젝트를 보면서 이해할 수 있을 정도로 공부해보고 내 프로젝트에 적용한 것은 추후에 업데이트 하겠다~^^
■ 1. Hilt 란? ■
Hilt를 알기위해서는 먼저 의존성 주입(Dependency Injection)에 대해 알아야한다.
먼저, 의존성 주입에 대해 공부를 해보자.
■ 1-1. 의존성 주입(Dependency Injection) 이란?
결과부터 말하자면 의존성 주입이란, 클래스간의 의존성(클래스간에 참조하는 것이 있다고 생각하면 쉬울듯)을 클래스 외부에서 주입하는 것을 뜻한다.
==> 의존성 주입을 구체적으로 말해본다면, 클래스에 대한 의존성을 인터페이스화를 통해 코드 유연성을 증대 시키며 클래스의 인스턴스를 외부에서 생성하여 주입을 하는것이라고 말할 수 있다.
■ 1-1-1. 원초적으로 의존성이 뭔데 ?
개발에서의 의존성이라는 것은 클래스간에 의존 관계가 있다는것, 즉 서로가 참조하여 쓴다라는 것을 뜻한다.
==> 그렇기에 의존관계(의존성)가 있다는 것은 어떠한 클래스가 바뀔 때 의존 관계에 있는 클래스 또한 영향을 받는다는 것이 중요한 것이다.
EX) 내가 어떠한 회사에 DB를 MySQL을 사용하고 있다고 하자. 그런데 회사 정책이 바뀜에 따라 Firebase로 교체하라고 한다..
아니 뭐냐?.. 나는 MySQL 부분의 코드만을 고쳐 Firebase로 교체하고 싶은데.. 회사의 코드가 의존성이 엄청 높게 코드를 짜 모든 부분을 건들여서 수정해야한다. 의존성이 무슨말인 지 알겠지?
■ 1-1-2. 그러면 의존 관계를 줄일 순 없나?(feat. 인터페이스)
자바 공부할 때 많이 배웠던 인터페이스가 나왔다! 맞다. 인터페이스를 이용하여 클래스로부터 의존성을 없애는 것이다.
==> 그러니까 특정 기능을 하는 클래스가 있다면, 클래스 기준으로 특정 기능의 인터페이스에서 구현해야만 하는 함수를 직접 정의해줌으로써 의존성을 없앨 수 있다.
EX) 위의 예시를 가지고 와서 DB 인터페이스를 만들고 Firebase 클래스에서 DB 인터페이스를 구현하고, MySQL 클래스에서 DB 인터페이스를 구현하면 서로 각각 인터페이스에서 구현하라고 하는 대로 만들었기 때문에 딱히 누구를 의존하지를 않는다. 즉, 클래스로부터 의존성을 없앤 것 이다.
■ 1-1-3. 원초적으로 주입은 뭔데 ?
개발에서의 주입이란 클래스 외부에서 객체를 생성하고, 이렇게 외부에서 생성한 객체를 클래스 내부에 주입하는 것이다.
EX) DB를 사용하는 클래스가 여러 개가 있다. 이때 firebase를 공통으로 사용한다고 치자.(Firebase는 인터페이스를 구현한 클래스라고 가정)
그러면 DB 클래스 내부에서 firebase가 인스턴스화된다면 DB 클래스 내부마다 Firebase클래스로부터 firebase객체를 생성하여 들고 있게 되는 것이다.
==> 이렇게 되면 DB가 Firebase에 강하게 결합되어 있다고 말할 수 있다.
왜냐하면 이 Firebase를 사용하던 DB 클래스들이 갑자기 MySQL로 바꾼다고 생각하보자.(MySQL는 인터페이스를 구현한 클래스라고 가정)
그러면 객체를 생성할때 MySQL클래스로부터 객체를 생성하기 때문에 기존의 Firebase를 통한 객체 생성문을 MySQL클래스로부터의 객체 생성문으로 다 바꿔야하는 것이다.
==> 이것을 해결하기 위해 주입을 사용하는 것이다!
즉, 외부에서 val DB = MySQL() 라는 DB 인스턴스를 만들어 DB가 필요한 각 클래스에 주입하는것이다!!
이러한 MySQL() 인스턴스를 저장하는 공간을 우리는 Container라고 부른다. 당연한 말이겠지만, 객체에 대한 제어 권한은 DB를 사용하려는 클래스에 있는것이 아닌 MySQL() 인스턴스를 정하는 공간인 Container에 있다.
이때 위에 있는 내용을 부르는 전문 용어가 있는데, IOC(Inversion of Control) 제어의 역전이라 부르며 인스턴스를 저장하는 Container를 IOC Container라고 부른다.
■ 1-1-4. 의존성 주입의 영향은 뭔데?
위에서 말한 내용인 개발자에게 편하게 해주는것보다 더 중요한 사실이 있다.
의존성 주입을 받는다면, 가장 큰 목적인 클래스간의 결합도가 약해진다.
==> 클래스간의 결합도가 약해진다는 것은 한 클래스가 변경될 경우, 다른 클래스가 변경될 필요성이 적어진다는 뜻이다. 그러므로 다음과 같은 이점들이 발생하는 것이다.
- 클래스간의 결합도가 약해져, 유지보수가 쉬워진다.
- 클래스간의 결합도가 약해져, 특정 클래스를 테스트하기 편해진다.
- 인터페이스 기반 설계는 코드를 유연 및 확장을 쉽게한다.
- UI가 있는 프로그램에서는 생명주기가 중요한데, 생명주기 별로 Container를 관리할 수 있게 된다면, 리소스의 낭비를 막을 수 있다.
■ 1-2. Hilt란 ?
Hilt는 안드로이드를 위한 의존성 주입 라이브러리이다.
==> 안드로이드 위한 의존성 주입 라이브러리이기 때문에 안드로이드 클래스에 생명주기를 고려한 의존성 주입을 할 수 있는 라이브러리이다.
기존에는 dagger라는 의존성 라이브러리가 있었으나, 러닝커브도 높고 dagger에서 안드로이드의 생명주기를 고려하는 의존성 주입은 없어서 따로 구현을 해야 했다.
==> Hilt는 dagger의 이러한점을 보완해서 나온것이다. 보완해서 나온것이기에 훨씬 간편하고 쉬운 특징도 가지고 있다.
■ 1-2-1. Hilt의 특징
- Dagger2 기반의 라이브러리
- Dagger와 다르게 직접적으로 인스턴스화 할 필요 없음(바이트 코드 변환을 사용하기 때문)
- 표준화된 Dagger2 사용법을 제시
- 보일러플레이트 코드가 감소
- 프로젝트 설정 간소화
- 쉬운 모듈 탐색과 통합
- 개선된 테스트 환경
- Android Studio 지원
- AndroidX 라이버러리의 호환
- Jetpack 라이브러리 클래스를 위해 extension 제공(ex: ViewModel, WorkManager)
■ 1-2-2. Hilt 사용법
■ 1-2-2-1. @HiltAndroidApp
이 어노테이션을 적용하면 Hilt 코드를 자동으로 생성 가능하게 해준다.
Hilt를 사용할 때 제일 처음에 해줘야하는 어노테이션이다.
■ 1-2-2-2. @AndroidEntryPoint
이 어노테이션을 사용하는 안드로이드 클래스는 해당 클래스의 생명주기를 따르는 의존성 컨테이너를 만든다.
[ *컨테이너 : 각 의존성들을 가지고 또 필요한 곳에 생명주기를 고려하여 의존성 주입을 하는 기능을 가짐 ]
각 안드로이드 클래스와 짝이 맞는 hilt component를 생성한다.
(컴포넌트는 컴포넌트 안에 있는 의존성들을 안드로이드 클래스로 주입할 수 있는(보낼 수 있는) 집합 장소라고 생각하면 된다.)
액티비티 중 ComponentActivity를 상속하는 액티비티만 지원한다.
프래그먼트 중 androidx의 프래그먼트를 상속한 애들만 지원한다.
■ 1-2-2-3. @Inject
이 어노테이션은
filed injection(hilt가 주입 가능한 것을 내가 변수에 넣어서 hilt로부터 주입을 받아서 사용) ==> hilt에게 주입 받아 사용하는 것
ex) @Inject lateinit var a: aClass
constructor injection(hilt가 어떻게 주입을 해줘야 하는지 알려주는 것) ==> hilt에게 의존성을 주입해주는 것(바인딩)
ex) class aClass @Inject constructor() {
}
의존성을 주입시키려면 hilt가 어떻게 주입을 해줘야 하는지 알아야한다.!!! (위에서 말한 constructor injection)
==> 의존성 주입 방식에 2가지 경우가 있다.
- constructor inject 할 수 있는 클래스 : 내가 구현한 클래스
- constructor inject 할 수 없는 클래스 : 인터페이스 or abstract 구현체(@Module, @Binds 사용), 외부 라이브러리(@Module, @provides 사용)
- hilt-moudle : @Module과 @InstallIn 필요하다.
- @Module : hilt-module임을 가리키는 역할(hilt가 알 수 있게)
- @InstallIn : 어느 안드로이드 클래스(activity,fragement, singlton(전역))을 사용할 건지 가리킨다.
- abstract class or 인터페이스 : @Binds
- 구현체(리턴값)가 되는 파라미터를 하나만 가질 수 있다.
- static이 아니기에 메모리적으로 효율적이다.
- 따로 구현이 필요 없을 경우 @Provides대신 사용한다.
- 간결하다. - 외부 라이브러리(external library), object : @Provides
- 구현에 필요한 파라미터를 여러개 혹은 가지지 않아도 된다.
- 구현 로직을 짜야만 한다.
- 해당 의존성을 사용할 때 매번 호출한다. - 즉, abstract class에서 @Provides를 사용할 수 없고, object에서 @Binds를 사용할 수 없다.
■ 1-2-2-4. @EntryPoint
이 어노테이션은 Hilt가 지원하는 않는 클래스에서 의존성이 필요한 경우 사용한다.
인터페이스에서만 사용한다. 반드시 @InstallIn이 함께 있어야한다. 즉, 인터페이스이면서 @InstallIn이 함께 있어야한다.
EntryPoints 클래스의 정적 메서드를 통해 그래프에 접근한다.
(Ex: ContentProvider, DFM, Dagger를 사용하지 않는 3rd-party 라이브러리)
■ 1-2-3. Hilt의 @Provides vs @Binds
위에서 말했다시피 @Provides는 외부 라이브러리 즉, 내가 구현하지 않은 객체를 매개변수로 사용할 때 사용하고 @Binds는 인터페이스의 경우 사용한다고 말했다.
기능적인 부분으로 말하자면, @Binds가 기능면에서 훨씬 도움이 된다.
==> 왜냐하면 같은 기능을 구현한 @Binds와 @Provides를 비교를 해보면 @Provides는 약간의 오버헤드를 발생시킨다.
실제로 @Provides 통해 작성된 코드를 보게 되면 존재하지 않는 클래스인 Factory 클래스를 생성하게 된다. 즉, 모듈을 @Provides를 통해 만든것이 많다고하면 존재하지 않는 클래스인 Factory 클래스가 엄청나게 많이 불필요하게 생겨난다는것이다. 이것은 앱의 크기에 얼마나 영향을 줄 지 생각을 해보아라.
하지만 @Binds로 구현을 하면 좋기는 한데 제약조건이 있다.
==> 반환 유형에 할당할 수 있는 유형의 매개변수가 하나만 있어야 한다는 점이다. 이러한 규칙을 잘 지켜서 @Binds로 바인딩을 하게 된다면 훨씬 효율적일 것이다.
■ 2. Hilt 적용된 앱 해석하기(feat, MovieApp) ■
참고