의존이란 각각의 클래스가 다른 클래스를 알고있는 관계를 말한다.
class ViewModel{
private val firebaseRepository = FirebaseRepository()
private fun loadLanguages() {
firebaseRepository.loadLanguages({list -> println(list.toAString())})
}
}
class FirebaseRepository {
fun loadLanguages(callback: (List<String>) -> Unit) {
// Firebase에서 언어 목록을 가져와 callback을 실행
}
}
ViewModel을 Firebase에 의존하지만 Firebase는 ViewModle을 알지 못한다.
이때 어떤 문제가 생길까?
새로운 repository 클래스를 생성하여 새로 구현해야 한다.
이러한 문제는 인터페이스를 사용해 같은 모양의 메소드를 구현하도록 강제하여 해결할 수 있다.
class ViewModel(private val repository: Repository){
private fun loadLanguages() {
firebaseRepository.getLanguages({list -> println(list.toAString())})
}
}
interface Repository {
fun getLanguages(callback: (List<String>) -> Unit)
}
class FirebaseRepository : Repository {
fun getLanguages(callback: (List<String>) -> Unit) {
// Firebase에서 언어 목록을 가져와 callback을 실행
}
}
class RemoteRepository: Repository {
fun getLanguages(callback: (List<String>) -> Unit) {
// Reat API 호출을 통해 언어 목록을 가져와 callback을 실행
}
}
그리고 ViewModel의 생성자로 repository의 값을 전달받아 repository의 변경이 있을 때마다 ViewModel의 코드를 변경하지 않을 수 있다.
💡 개발 중 변동이 잦은 클래스를 참조하는 것은 지양하고 비교적 안정적인 인터페이스를 참조하는 것이 좋다.상황에 맞게 ViewModel에 전달되는 repository를 코드상에서 직접 수정한다면 오류가 발생할 위험이 크다. → 따라서 상황에 맞게 의존성을 주입해 줄 라이브러리의 사용이 필요해진다.
Dagger는 자바에서 사용할 수 있는 의존성 라이브러리이다. Hilt는 Dagger를 기반으로 만들어 졌다.
Inject - 의존성 주입 요청, 객체 생성 방법 안내
Module - 객체 생성
Component (container) - Module이 생성한 객체를 Inject에 주입
우선 Aplication클래스에 @HiltAndroidApp 을 달아주어야 한다.
@HiltAnodroidApp
class MyApplication: Application(){}
가장 처음의 코드를 확인해 보면 코드에서 MainActivity는 뷰모델을, 뷰모델은 레포지토리를 의존하고 있었다. 뷰모델과 레포지토리가 생성방법을 Hilt에게 알려 준다.
@HiltViewModel
class ViewModel @Inject (private val firebaseRepository: FirebaseRepository ){
private fun loadLanguages() {
firebaseRepository.getLanguages({list -> println(list.toAString())})
}
}
class FirebaseRepository @Inject constructor() {
fun getLanguages(callback: (List<String>) -> Unit) {
// Firebase에서 언어 목록을 가져와 callback을 실행
}
}
@AndoridEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
...
}
힐트를 사용해 의존성 주입을 요청할 때에는 @Inject 를 사용해 주어야 하지만 액티비티와 프래그먼트에서는 activity-ktx, fragment-ktx를 사용해 생략할 수 있다.
dependencies {
implementation 'androidx.fragment:fragment-ktx:1.3.6'
implementation 'androidx.activity:activity-ktx:1.3.1'
}
힐트에게 뷰모델, 안드로이드 컴포넌트임을 알려주기 위해 각각 @HiltViewModel, @AndroidEntryPoint 어노테이션을 달아 준다.
두번째 경우로 수정한 코드를 확인해 보면 interface타입을 사용하여 repository를 정의하였다.
힐트가 객체 생성방법을 유추할 수 없게 된 상황이다. 이 경우 힐트가 객체를 주입할 수 있도록 bind module을 정의해 주어야 한다.
@HiltViewModel
class ViewModel @Inject (private val repository: Repository ){
private fun getLanguages() {
repository.getLanguages { }
}
}
interface Repository {
fun getLanguages(callback: (List<String>) -> Unit)
}
class RemoteRepository @Inject constructor(): Repository {
fun getLanguages(callback: (List<String>) -> Unit) {
// Reat API 호출을 통해 언어 목록을 가져와 callback을 실행
}
}
@Module
@InstallIn(ViewModelComponent::class)
abstract class MyModule {
@Binds
abstract fun bindRepository(remoteRepository: RemoteRepository): Repository
}
@Module 어노테이션을 사용해 모듈 클래스 임을 표시하고 추상 클래스로 생성한다.
주입할 인터페이스의 구현 클래스를 매개변수로 받고 인터페이스 타입의 리턴을 반환하는 추상 매서드를 생성해 준다. 이때도 @Binds 어노테이션을 사용해 표시해 준다.
이때 같이 사용된 @InstallIn 어노테이션은 Module과 Component를 연결해 주는 역할을 한다.
힐트에는 안드로이드의 생명주기에 맞춰 미리 정의되어 있는 Component들이 있고 그 Component들이 안드로이드의 생명주기에 따라 생성되고 사라지면서 알맞게 의존성을 주입해 주는 것이다.
상황에 맞는 Component를 찾아 InstallIn에 넣어주면 된다.
Provide Module을 생성하여 힐트에게 알려 준다.
Bind를 사용한 것과 다르게 일반 클래스와 일반 메소드를 사용해 구성한다.