코린이가 보는 힐트이다.
계속해서 수정을 해 나아가야하는 게시글, 그리고 꼭 익혀야하는 글이다.
잊지 말아야 할 기본 동작 순서
1번 : 재료를 불러올 인터페이스 +
2번 : 각 재료들을 생성자로 Inject받아서 무엇을 할지 구체화 할 클래스(있어도되고 없어도되고)
→
3번 : 재료들을 구체화하는 모듈 + 모듈에 불러온 재료들로 완제품을 추상화 함수
→
4번 : 모듈과 연결하는 컴포넌트 : 모듈과 연결되어있어 완제품 추상화를 구체화시키는 공간 (구체화 한 객체를 가지고 있다.)
→
5번 : 사용할 공간에서 컴포넌트를 호출하고 객체를 가져온다. + 마음대로 활용 시작.
Dagger2 기반의 안드로이드
표준화된 Dagger 사용법을 제시
보일러플레트 코드 감소
프로젝트 설정 간소화
쉬운 모듈탐색과 통합환경 제공
개선된 테스트 환경
Hilt의 주요 어노테이션
HiltApplicationComponent를 생성 및 인스턴스화 해준다.
onCreate에서 일어난다.
→Bytecode transformation 덕분에 가능하다.
→어노테이션을 달아주면 앞에 접두어로 Hilt_가 붙은 클래스를 생성해준다.
→ 이곳에 Component생성 및 인스턴스 코드가 자동으로 들어간다.
→
소스코드가 바이트 코드를 생성하고 Gradle이 바이트코드를 변환(읽을때) 바이트코드는 Hilt_접두어가 붙은클래스를 상속하고 있기 때문이다.
HiltAndroidApp 어노테이션을 사용한다면 다음 스텝으로 사용해야한다.
사용하면 안드로이드 클래스에 DI컨테이너를 추가하게된다.
Dagger2의 관점에서 보면 HiltAndroidApp은 Component를 생성하게 되는 것이고
AndroidEntryPoint는 Subcomponent를 생성하는 것이다.
힐트는 다음과 같은 컴포넌트를 제공하며 화살표의 방향은 상위 →하위 이다.
힐트는 이미 표준화된 스코프를 제공하고 있다.
이를 이용하면 모듈클래스에서 스코프 어노테이션을 이용하여 동일 인스턴스를 공유할수 있다.
예를 들어보자.
먼저 ApplicationComponent로부터 주입받은 MemoRepository가 MemoActivity에 존재한다.
만약 DetailActivity에서 동일한 Repository를 요청하게 된다면 새로운 MemoRepository를 생성하기때문에 서로 다른 인스턴스가 된다.
만약 MemoRepository에 @Singleton어노테이션을 추가한다면 어떻게 될까?
(어노테이션을 추가할때는 무엇과 쌍을 이루는지 살펴야 한다. ActivityComponent에는 ActivityScoped를
ApplicaionComponent에는 Singleton을 사용해야한다.)
이렇게 어노테이션을 달아놓으면 동일한 인스턴스를 주입한다.(자원 공유를 쉽게한다.)
Hilt를 사용하려면 사용하는 위치에 @AndroidEntryPoint 어노테이션을 붙여주어야 한다.
만약 Fragment에서 사용하려면 Fragment 상위에 존재하는 Activity에도 붙여주어야 한다.
Fragment에만 붙여주고 상위의 Activity에 붙여주지 않는다면 앱이 종료가 된다.
Hilt Module
-@Installin
@InstallIn(ApplicationComponent::class)
@Module
object DataModule {
@Provides
fun provideMemoDB(@ApplicationContext context : Context)=
Room.databaseBuilder(context,MemoDatabase::class.java,"Memo.db").build()
}
Installin은 어떤 컴포넌트에 설치할것인지 명시해야한다.
Hilt는 이것을 보고 컴파일타입에 관련 코드를 생성한다.→컴파일 에러 위험
@EntryPoint
Hilt가 지원하지 않는 클래스에서 DI가 필요한 경우 사용.
특징
Interface에서만 사용가능하다.
Installin과 함께 사용하여 어떤 컴포넌트에 접근할지 명시
EntryPoint클래스에 정적메소드를 통해 해당 컴포넌트 그래프에 접근 할 수 있다.
→ 요약
실습 예제
상황 - Fragment에서 Epoxy를 호출한다.
Epoxy는 Controller와 Factory로 이루어져있다.
Controller에서는 Factory에서 생성된 아이템을 받아와서 배치를 한다.
Hilt를 사용하는 이유 : Api호출을 위한 ApiService를 Hilt를 이용해 생성하고 있다.
요약 : Api호출을 위한 service를 Factory에 주입해야한다.
하지만 Factory는 Hilt에서 지원하는 AndroidEntryPoint가 아니다.
즉 → EntryPoint를 이용해야한다.
//먼저 Epoxy가 호출되는 Fragment 상단에 AndroidEntryPoint를 달아준다.
@AndroidEntryPoint
class TestFragment : Fragment() {
//외부에서 생성한 service이다. 그리고 우리의 이동시킬 목표이다.
@Inject
lateinit var service: ApiService
//기억해두자. TestEpoxyControoler에 requireContext()를 생성자로 함으로써 fragment의 생성자를
//넘겨주고있다.
//1-1
private val viewPagerController: TestEpoxyController by lazy {
TestEpoxyController(requireContext)
}
//Epoxy컨트롤러이다. context를 fragment로부터 넘겨받았다. 1-1
class TestEpoxyController(fragmentContext :Context) : EpoxyController() {
//viewpager에 필요한 Item들을 Factory로부터 생성하고있다.
//최종 목적지인 Factory에 Fragment로부터 가져온 context를 넘겨준다.
//fragmentContext
//1-2
private val viewpagerItem: List<ItemViewpager> =
HomeViewPagerFactory(fragmentContext).getViewPagerItems(10)
//Epoxy 팩토리이다. context를 여기까지 가져왔다.
class HomeViewPagerFactory(fragmentContext : Context) {
//먼저 코루틴을 launch시킬 job을 만든다.
val job = CoroutineScope(Dispatchers.IO + SupervisorJob())
//EntryPoint로 Hilt에게 위치를 알려준다. << 여기서 Hilt를 쓰겠습니다.
@EntryPoint
@InstallIn(ApplicationComponent::class)
interface TestFragmentEntryPoint {
fun getService(): ApiService
}
//getService를 구체화시켜준다.
private fun getService(fragmentContext: Context):ApiService{
//다음의 파라미터들은 hilt-component가 알 수 있는 값들이어야 한다.
//fragmentContext를 넣어준다. 이곳에 context를 넣어주기위해 계속 가져온것이다.
return EntryPointAccessors.fromApplication(
fragmentContext, TestFragmentEntryPoint::class.java
).getService()
}
//가져온 service 여기있다 ㅜㅜ 힘들었다.
private val service by lazy {
getService(context)
}
//서비스를가져왔으니 사용해보자.
private suspend fun getVideos(){
val videos = service.getVideo()
}
}