[Android] Dependency Injection Part 3. Dagger basics

[Android] Dependency Injection Part 3. Dagger basics

이 글은 Android Developer κ°€μ΄λ“œ λ‚΄μš©μ„ ν† λŒ€λ‘œ μž‘μ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 예제 μ½”λ“œλŠ” Kotlin만 κ°€μ Έμ™”μœΌλ©°, Java μ½”λ“œλŠ” μ›λ¬Έμ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

직접 μ˜μ‘΄μ„± μ£Όμž…μ΄λ‚˜ Service locator μ‚¬μš©μ€ ν”„λ‘œμ νŠΈ 크기에 따라 λ¬Έμ œκ°€ 생길 μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. Daggerλ₯Ό μ‚¬μš©ν•΄ λ””νŽœλ˜μ‹œλ₯Ό κ΄€λ¦¬ν•˜λ©΄ ν”„λ‘œμ νŠΈμ˜ 규λͺ¨κ°€ 컀지더라도 λ³΅μž‘μ„±μ„ μœ μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Dagger의 μž₯점

DaggerλŠ” 번거둭고 μ—λŸ¬κ°€ λ°œμƒν•˜κΈ° μ‰¬μš΄ λ³΄μΌλŸ¬ν”Œλ ˆμ΄νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•˜μ§€ μ•Šλ„λ‘ μ•„λž˜ κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€.

  • Part 2μ—μ„œ 직접 κ΅¬ν˜„ν–ˆλ˜ AppContainer μ½”λ“œ(application graph) 생성
  • Application graphμ—μ„œ μ‚¬μš© κ°€λŠ₯ν•œ ν΄λž˜μŠ€λ“€μ— λŒ€ν•œ νŒ©ν† λ¦¬ μ½”λ“œ 생성 βž” λ‚΄λΆ€μ μœΌλ‘œ 각 μ˜μ‘΄μ„±μ„ λͺ¨λ‘ λ§Œμ‘±μ‹œν‚΄
  • Scope을 μ‚¬μš©ν•˜μ—¬ νƒ€μž…μ„ μ–΄λ–»κ²Œ μ •ν–ˆλŠλƒμ— 따라 λ””νŽœλ˜μ‹œλ₯Ό μž¬μ‚¬μš©ν•˜κ±°λ‚˜ μƒˆ μΈμŠ€ν„΄μŠ€ 생성
  • 이전 파트의 둜그인 ν”Œλ‘œμš° μ˜ˆμ œμ—μ„œμ²˜λŸΌ Dagger subcomponentλ₯Ό μ΄μš©ν•˜μ—¬ νŠΉμ • ν”Œλ‘œμš°μ— λŒ€ν•œ μ»¨ν…Œμ΄λ„ˆ 생성 βž” 더 이상 ν•„μš”μ—†λŠ” μžμ›μ„ λ©”λͺ¨λ¦¬μ—μ„œ ν•΄μ œν•˜μ—¬ μ•± μ„±λŠ₯ ν–₯상에 도움

DaggerλŠ” 이 λͺ¨λ“  μž‘μ—…μ„ λΉŒλ“œ νƒ€μž„μ— μžλ™μœΌλ‘œ μˆ˜ν–‰ν•˜λ©°, 이전 νŒŒνŠΈμ—μ„œ μˆ˜λ™μœΌλ‘œ μž‘μ„±ν–ˆλ˜ 것과 λΉ„μŠ·ν•œ μ½”λ“œλ“€μ„ μƒμ„±ν•©λ‹ˆλ‹€. λ‚΄λΆ€μ μœΌλ‘œ, DaggerλŠ” 클래슀 μΈμŠ€ν„΄μŠ€λ₯Ό 제곡 μ‹œ μ°Έμ‘°ν•  수 μžˆλŠ” κ·Έλž˜ν”„λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€. κ·Έλž˜ν”„μ˜ λͺ¨λ“  ν΄λž˜μŠ€λ“€μ— λŒ€ν•΄ νŒ©ν† λ¦¬ νƒ€μž… 클래슀λ₯Ό μƒμ„±ν•˜μ—¬ ν•΄λ‹Ή νƒ€μž…μ˜ μΈμŠ€ν„΄μŠ€λ₯Ό κ°€μ Έμ˜¬ λ•Œ λ‚΄λΆ€μ μœΌλ‘œ μ‚¬μš©ν•©λ‹ˆλ‹€.

λΉŒλ“œ νƒ€μž„μ— DaggerλŠ” μ½”λ“œλ₯Ό νƒμƒ‰ν•˜κ³ ,

  • λ””νŽœλ˜μ‹œ κ·Έλž˜ν”„ 생성 및 μ•„λž˜ ν•­λͺ©μ— λŒ€ν•΄ κ²€μ¦ν•©λ‹ˆλ‹€:
    • λͺ¨λ“  객체의 λ””νŽœλ˜μ‹œκ°€ Runtime Exception 없이 λ§Œμ‘±ν•˜λŠ”μ§€
    • λ””νŽœλ˜μ‹œ 사이클이 μ—†λŠ”μ§€ (λ¬΄ν•œ 루프 μ°Έμ‘°κ°€ 없도둝)
  • λŸ°νƒ€μž„μ— μ‚¬μš©λ  ν΄λž˜μŠ€μ™€ κ·Έλ“€μ˜ λ””νŽœλ˜μ‹œλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

κ°„λ‹¨ν•œ Dagger use case: νŒ©ν† λ¦¬ 생성

Daggerλ₯Ό μ–΄λ–»κ²Œ μ‚¬μš©ν•˜λŠ”μ§€ μ•ŒκΈ° μœ„ν•΄, μ•„λž˜ λ‹€μ΄μ–΄κ·Έλž¨μ˜ UserRepository처럼 κ°„λ‹¨ν•œ νŒ©ν† λ¦¬λ₯Ό μƒμ„±ν•΄λ΄…μ‹œλ‹€.

UserRepositoryλŠ” μ΄λ ‡κ²Œ μ •μ˜ν•©λ‹ˆλ‹€.

class UserRepository(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }


UserRepository의 μƒμ„±μžμ— @Inject μ–΄λ…Έν…Œμ΄μ…˜μ„ μΆ”κ°€ν•˜λ©΄ UserRepository μΈμŠ€ν„΄μŠ€κ°€ μ–΄λ–»κ²Œ μƒμ„±λ˜λŠ”μ§€ Dagger κ°€ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

// @Inject lets Dagger know how to create instances of this object
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }


μœ„ μ½”λ“œ μŠ€λ‹ˆνŽ«μ—μ„œ Daggerμ—κ²Œ μ•Œλ €μ£ΌλŠ” 것은 두 κ°€μ§€μž…λ‹ˆλ‹€.

  1. @Inject μ–΄λ…Έν…Œμ΄μ…˜μ΄ μΆ”κ°€λœ μƒμ„±μžλ₯Ό 톡해 UserRepositoryκ°€ μ–΄λ–»κ²Œ μƒμ„±λ˜λŠ”μ§€
  2. UserRepository의 λ””νŽœλ˜μ‹œλ“€: UserLocalDataSource, UserRemoteDataSource

이제 DaggerλŠ” UserRepository μΈμŠ€ν„΄μŠ€λ₯Ό μ–΄λ–»κ²Œ 생성해야 ν•˜λŠ”μ§€ μ•Œμ§€λ§Œ 그의 λ””νŽœλ˜μ‹œλ“€μ„ μ–΄λ–»κ²Œ 생성해야 ν•˜λŠ”μ§€λŠ” μ•Œ 수 μ—†μŠ΅λ‹ˆλ‹€. λ‹€λ₯Έ ν΄λž˜μŠ€λ“€μ—λ„ λ˜‘κ°™μ΄ μ–΄λ…Έν…Œμ΄μ…˜μ„ μΆ”κ°€ν•œλ‹€λ©΄, 그듀에 λŒ€ν•΄μ„œλ„ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

// @Inject lets Dagger know how to create instances of these objects
class UserLocalDataSource @Inject constructor() { ... }
class UserRemoteDataSource @Inject constructor() { ... }


Dagger components

DaggerλŠ” ν”„λ‘œμ νŠΈμ˜ λ””νŽœλ˜μ‹œ κ·Έλž˜ν”„λ₯Ό μƒμ„±ν•˜μ—¬, 각 λ””νŽœλ˜μ‹œκ°€ ν•„μš”ν•  λ•Œ μ–΄λ””μ—μ„œ 가져와야 ν•˜λŠ”μ§€ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. Daggerκ°€ 이 κ·Έλž˜ν”„λ₯Ό μƒμ„±ν•˜κΈ° μœ„ν•΄μ„œλŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό μƒμ„±ν•˜κ³ , κ·Έ μΈν„°νŽ˜μ΄μŠ€μ— @Component μ–΄λ…Έν…Œμ΄μ…˜μ„ λΆ™μ—¬μ€˜μ•Ό ν•©λ‹ˆλ‹€. 그럼 DaggerλŠ” μˆ˜λ™ λ””νŽœλ˜μ‹œ μ£Όμž…μ—μ„œ 직접 ν–ˆλ˜ 것과 같은 μ»¨ν…Œμ΄λ„ˆλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

@Component μΈν„°νŽ˜μ΄μŠ€ μ•ˆμ— ν•„μš”ν•œ 클래슀(예, UserRepository)의 μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜λ₯Ό μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€. @Component μ–΄λ…Έν…Œμ΄μ…˜μ€ Daggerκ°€ μƒμ„±ν•΄μ•Όν•˜λŠ” μ»¨ν…Œμ΄λ„ˆλ₯Ό λ‚˜νƒ€λƒ…λ‹ˆλ‹€. 이것을 Dagger component라고 ν•˜λ©°, μ–΄λ–»κ²Œ 생성할지와 각 λ””νŽœλ˜μ‹œμ— λŒ€ν•΄ Daggerκ°€ μ•Œκ³  μžˆλŠ” κ°μ²΄λ“€λ‘œ κ΅¬μ„±λœ κ·Έλž˜ν”„λ₯Ό ν¬ν•¨ν•©λ‹ˆλ‹€.

// @Component makes Dagger create a graph of dependencies
@Component
interface ApplicationGraph {
    // The return type  of functions inside the component interface is
    // what can be provided from the container
    fun repository(): UserRepository
}


ν”„λ‘œμ νŠΈλ₯Ό λΉŒλ“œν•  λ•Œ DaggerλŠ” ApplicationGraph μΈν„°νŽ˜μ΄μŠ€μ˜ κ΅¬ν˜„μ²΄λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€: DaggerApplicationGraph. μ˜ˆμ œμ—μ„œ DaggerλŠ” 3가지 클래슀(UserRepository, UserLocalDataSource, UserRemoteDataSource)κ°„μ˜ κ΄€κ³„λ‘œ κ΅¬μ„±λœ κ·Έλž˜ν”„λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€. ApplicationGraphλŠ” μ•„λž˜μ™€ 같이 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

// Create an instance of the application graph
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()
// Grab an instance of UserRepository from the application graph
val userRepository: UserRepository = applicationGraph.repository()


DaggerλŠ” applicationGraph.repository()κ°€ 호좜될 λ•Œλ§ˆλ‹€ μΈμŠ€ν„΄μŠ€λ₯Ό μƒˆλ‘œ μƒμ„±ν•©λ‹ˆλ‹€.

val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository != userRepository2)


κ²½μš°μ— 따라, μ»¨ν…Œμ΄λ„ˆμ— 같은 μΈμŠ€ν„΄μŠ€λ§Œ ν•„μš”ν•œ κ²½μš°λ„ μžˆμŠ΅λ‹ˆλ‹€. μ•„λž˜μ™€ 같은 μ΄μœ λ‘œμš”.

  1. ν•΄λ‹Ή νƒ€μž…μ„ λ””νŽœλ˜μ‹œλ‘œ κ°–λŠ” λ‹€λ₯Έ νƒ€μž…μ— 같은 μΈμŠ€ν„΄μŠ€λ₯Ό κ³΅μœ ν•˜κ³  싢을 λ•Œ (예, μ—¬λŸ¬ ViewModel에 λ™μΌν•œ UserDataμΈμŠ€ν„΄μŠ€λ₯Ό 곡유)
  2. 객체λ₯Ό μƒμ„±ν•˜λŠ” λΉ„μš©μ΄ μ»€μ„œ 맀번 μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜μ§€ μ•Šκ³ , ν•œ 번 μ •μ˜λœ μΈμŠ€ν„΄μŠ€λ₯Ό μ‚¬μš©ν•˜κ³  싢을 λ•Œ (예, JSON parser)

λ””νŽœλ˜μ‹œλ₯Ό 직접 μ£Όμž…ν•  λ•ŒλŠ” ViewModel의 μƒμ„±μžμ— λ™μΌν•œ UserRepository μΈμŠ€ν„΄μŠ€λ₯Ό μ „λ‹¬ν•˜λŠ” κ²ƒμœΌλ‘œ 이λ₯Ό κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ Daggerλ₯Ό μ‚¬μš©ν•  땐 μ½”λ“œλ₯Ό 직접 μž‘μ„±ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— Dagger에 같은 μΈμŠ€ν„΄μŠ€λ₯Ό μ›ν•œλ‹€λŠ” κ²ƒλ§Œ μ•Œλ €μ£Όλ©΄ λ©λ‹ˆλ‹€. 이것은 scope μ–΄λ…Έν…Œμ΄μ…˜μ„ μ΄μš©ν•˜μ—¬ ν‘œμ‹œν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Scoping with Dagger

Scope μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜μ—¬ ν•œ 객체의 λΌμ΄ν”„νƒ€μž„λΆ€ν„° κ·Έ μ»΄ν¬λ„ŒνŠΈμ˜ λΌμ΄ν”„νƒ€μž„κΉŒμ§€ μ œν•œν•  수 μžˆμŠ΅λ‹ˆλ‹€. 즉 λ””νŽœλ˜μ‹œκ°€ ν•„μš”ν•  λ•Œλ§ˆλ‹€ 같은 μΈμŠ€ν„΄μŠ€λ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

UserRepository의 μœ μΌν•œ μΈμŠ€ν„΄μŠ€λ₯Ό 가지기 μœ„ν•΄, javax.inject에 이미 μ •μ˜λœ @Singletone μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

// Scope annotations on a @Component interface informs Dagger that classes annotated
// with this annotation (i.e. @Singleton) are bound to the life of the graph and so
// the same instance of that type is provided every time the type is requested.
@Singleton
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

// Scope this class to a component using @Singleton scope (i.e. ApplicationGraph)
@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }


λ˜λŠ” μ»€μŠ€ν…€ scope μ–΄λ…Έν…Œμ΄μ…˜μ„ 직접 μƒμ„±ν•˜μ—¬ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ•„λž˜μ²˜λŸΌ scope μ–΄λ…Έν…Œμ΄μ…˜μ„ μƒμ„±ν•©λ‹ˆλ‹€.

// Creates MyCustomScope
@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class MyCustomScope


그리고 μ΄λ ‡κ²Œ μ‚¬μš©ν•©λ‹ˆλ‹€.

@MyCustomScope
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

@MyCustomScope
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val service: UserService
) { ... }


두 경우 λͺ¨λ‘ @Component μΈν„°νŽ˜μ΄μŠ€μ™€ λ™μΌν•œ scope μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€. 즉 applicationGraph.repository()κ°€ 호좜될 λ•Œλ§ˆλ‹€ 같은 UserRepository μΈμŠ€ν„΄μŠ€λ₯Ό μ–»κ²Œ λ©λ‹ˆλ‹€.

val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository == userRepository2)


κ²°λ‘ 

더 λ³΅μž‘ν•œ 상황에 μ μš©ν•˜κΈ° 전에, Dagger의 μž₯점과 κΈ°λ³Έ λ™μž‘ 방식을 μ΄ν•΄ν•˜λŠ” 것이 μ€‘μš”ν•©λ‹ˆλ‹€.

λ‹€μŒ νŒŒνŠΈμ—μ„œλŠ” Android 앱에 Daggerλ₯Ό μΆ”κ°€ν•˜λŠ” 방법에 λŒ€ν•΄ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€.

Comments