๊ฐœ๋ฐœ/Android

[Android] Dependency Injection Part 3. Dagger basics

๋„๋ฆฌ ๐ŸŸ 2021. 4. 9. 00:36

์ž‘์„ฑ์ผ: 2020.03.02


์ด ๊ธ€์€ 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๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.