Android에서 * DI (의존성 주입)를 도와주기 위한 라이브러리인 Hilt를 학습하여 사용 중이다.
* DI : https://hungseong.tistory.com/10
(1) constructor inject
Hilt를 사용 하면서, 다음과 같이 프로젝트 내에서 클래스의 인스턴스를 Hilt에게 바로 제공 할 수 있는 경우에는 따로 Module을 통해 Install 하지 않고 constructor inject을 통해 종속성을 제공해 줄 수 있다.
class DateFormatter @Inject constructor() {
@SuppressLint("SimpleDateFormat")
private val formatter = SimpleDateFormat("d MMM yyyy HH:mm:ss")
fun formatDate(timestamp: Long): String {
return formatter.format(Date(timestamp))
}
}
// DateFormatter Inject하여 사용
@AndroidEntryPoint
class LogFragment : Fragment() {
@Inject lateinit var dateFormatter: DateFormatter
...
}
그러나 Room, Retrofit과 같은 외부 라이브러리에서 제공되는 클래스이므로 프로젝트 내에서 소유할 수 없는 경우 또는 constructor를 가질 수 없는 인터페이스에 대한 종속성 삽입의 경우에는 위와 같은 constructor-inject를 통해 종속성을 제공할 수 없다.
이 때 Hilt Module을 추가하여 종속성을 제공 할 수 있는데, 제공해야 하는 종속성의 종류에 따라 @Binds와 @Provides로 나뉘게 된다.
(2) @Binds
@Binds는 constructor를 가질 수 없는 인터페이스에 대한 종속성 삽입의 경우에 사용한다.
// 인터페이스
interface LogRepository {
suspend fun add(msg: String)
suspend fun getLogs() : List<Log>
}
// 인터페이스 구현체
@Singleton
class LogDBRepository @Inject constructor(private val logDao: LogDao,
private val ioDispatcher : CoroutineDispatcher) : LogRepository {
override suspend fun add(msg: String) = withContext(ioDispatcher) {
logDao.add(Log(msg = msg))
}
override suspend fun getLogs(): List<Log> = withContext(ioDispatcher) {
logDao.getLogs()
}
}
다음과 같이 LogRepository 인터페이스를 구현하는 class는 다른 class에서 종속성이 주입되어 사용 시 LogRepository라는 인터페이스 타입을 가지게 된다.
@HiltViewModel
class LogViewModel @Inject constructor(@LogDB private val logRepository: LogRepository)
: ViewModel() {
...
}
LogRepository에 대한 constructor-inject 종속성 주입은 interface이므로 불가능하다. 이 때 Module을 생성하여 @Binds 어노테이션을 사용할 수 있다.
@InstallIn(SingletonComponent::class)
@Module
abstract class LogDBModule {
@Binds
@Singleton
abstract fun bindLogDBRepository(impl: LogDBRepository) : LogRepository
}
위와 같이 @Binds 어노테이션을 통해 LogRepository에 대한 종속성을 제공할 수 있다.
이 때, 함수의 반환 타입은 구현하고자 하는 interface type이며, 매개변수는 실제 제공하고자 하는 interface의 구현체 class이다.
@Binds 어노테이션을 사용하기 위해서는 모듈은 abstract class, 함수는 abstract function이어야 한다.
(3) @Provides
@Provides는 Room, Retrofit과 같은 외부 라이브러리에서 제공되는 클래스이므로 프로젝트 내에서 소유할 수 없는 경우 또는 Builder 패턴 등을 통해 인스턴스를 생성해야 하는 경우에 사용한다.
// Room Database
@Database(entities = [Log::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun logDao(): LogDao
}
// Use
val db = Room.databaseBuilder(context, Appdatabase::class.java, "name.db").build()
Room을 예시로 들 때, db를 생성 할 때 외부 라이브러리인 Room 내에서 builder 패턴을 이용하므로 constructor-inject를 통한 종속성 주입이 불가능하다. 이 때 @Module을 생성하여 @Provides 어노테이션을 사용할 수 있다.
@InstallIn(SingletonComponent::class)
@Module
object DBModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context) : AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"logdata.db"
).build()
}
}
이 때, 함수의 반환 타입은 제공하고자 하는 인스턴스의 type이며, 매개변수는 인스턴스 생성에 필요한 종속성, 함수 내부는 실제 인스턴스의 구현이다.
@Provides만 포함되는 Module의 경우 object 형태로 생성 했을 때 provider는 최적화 된 코드를 제공하며, inline 된 코드로 제공된다.
참고 : https://github.com/googlecodelabs/android-hilt
https://developer.android.com/training/dependency-injection/hilt-android
'Android' 카테고리의 다른 글
[Android, Kotlin] Coroutine Flow block 내에서 여러 개의 suspend function에 대한 비동기 처리 (2) | 2021.12.06 |
---|---|
[Android, Kotlin] ViewPager2의 currentItem 설정이 동작하지 않는 문제 (0) | 2021.12.02 |
[Android] Exception 종류와 내용 (2) | 2021.11.20 |
[Android, Kotlin] 멀티 스레드 간 통신을 위한 Handler, Looper (2) | 2021.11.20 |
[Android, Kotlin] Fragment Lifecycle 상황별 동작 순서 (add, replace, back stack) (0) | 2021.11.19 |