Dagger2는 객체 간의 종속성을 관리하는 코드를 자동으로 생성 하는 도구
Component :
클래스의 인스턴스를 모아놓는 역할 각 인스턴스들은 Module 단위로 선언하여 제공
Module :
@Provider 하기위한 메소드를 Module 클래스의 인스턴스를 모아 놓는 역활
Provider :
제공(provider) 하고 싶은 객체의 인스턴스 메소드에 붙이는 것으로 클래스에 무엇을 제공@Provider하고 싶은 지를 알리는 것
필드, 생성자(주생성자), 메서드에 붙여 Component
로 부터 의존성 객체를 주입 요청하는 annotation
@Inject
로 의존성 주입을 요청하면 연결된 Component
가 Module
로부터 객체를 생성하여 넘겨주고
Component
는 @Inject annotation
을 의존성 주입할 멤버변수와 생성자에 달아줌으로 DI 대상을 확인할 수 있음
객체(인스턴스)의 생성이 클래스에서 이루어지지 않고, Component
가 생성해줌
기본적으로 Dagger는 요청된 Type(자료형)에 맞게 Component
가 Module
로부터 객체를 생성하여 주입 함
그러나
Interface
와 Activity
와 Fragment
등의 Android 환경 자체 Lifecycle을 가진 객체에는 사용할 수 없음Car.kt
class Car @Inject constructor(val wheels: Wheels, val engine: Engine) {
companion object {
private const val TAG = "Dagger Car"
}
fun drive() {
Log.d(TAG, "나는 드라이버")
}
}
CarComponent.kt
@Component
interface CarComponent {
fun getCar() : Car
}
Engine.kt
class Engine @Inject constructor()
Wheels.kt
class Wheels @Inject constructor()
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Interface 구현하는 Dagger(xxx)Component 객체를 반환하는 create() 메서드를 제공
//이를 이용해 만들어진 DaggerCarComponent 인스턴스로 Dagger를 통해 객체가 생성되고 car 구성요소가 생성
val carComponent : CarComponent = DaggerCarComponent.create()
val car: Car = carComponent.getCar()
car.drive()
}
}
Provider
에 의해 만들어진 인스턴스나, 프로퍼티가 주입된 인스턴스를 반환하는 메소드 이며 생성된 Component
클래스에서 Provision
메서드를 통해 객체를 얻을 수 있음CarComponent.kt
@Component(modules = [WheelsModule::class])
interface CarComponent {
fun getCar() : Car
}
Wheels.kt
class Wheels @Inject constructor(val rims: Rims, val tires: Tires)
Rims.kt
class Rims {}
Tires.kt
class Tires {
fun inflateTires() {
Log.d(TAG, "Tires are inflated")
}
}
WheelsModule.kt
@Module
class WheelsModule {
@Module
companion object {
@JvmStatic
@Provides
fun provideRims() = Rims()
@JvmStatic
@Provides
fun provideTires(): Tires {
val tires = Tires()
tires.inflateTires()
return tires
}
@JvmStatic
@Provides
fun provideWheels(rims: Rims, tires: Tires) = Wheels(rims, tires)
}
}
Member Injection
에는 인스턴스를 만든 다음 Provider
에 의해 제공되는 인스턴스 이며 의존성을 주입시킬 객체를 메서드의 파라미터로 넘기는 방식
두가지 방식으로 주입 되며 필드주입, 메서드 주입 방식
Field Injection
CarComponent.kt
@Component
interface CarComponent {
fun getCar() : Car
fun inject(mainActivity: MainActivity)
}
CarComponent.kt
class MainActivity : AppCompatActivity() {
//멤버 인젝션(Member Injection)메서드
// -> 인스턴스를 만든 다음 Provider에 의해 제공되는 인스턴스를 주입시킴
//필드 주입(Field Injection)
// -> 필드가 정의된 인스턴스를 필드 자체에 값을 주입하는 방식
@Inject
lateinit var car : Car
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val carComponent : CarComponent = DaggerCarComponent.create()
carComponent.inject(this)
car.drive()
}
Mehtod Injection
Car.kt
const val TAG = "Dagger Car"
class Car @Inject constructor(val wheels: Wheels, val engine: Engine) {
fun drive() {
Log.d(TAG, "나는 드라이버")
}
@Inject
fun connectRemote(remote: Remote) {
remote.enableRemote(this)
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
//메서드 주입(Method Injection)
// -> 메서드의 파라미터 입력 값에 Provider에 제공하는 인스턴스를 주입하는 방식
@Inject
lateinit var car : Car
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val carComponent : CarComponent = DaggerCarComponent.create()
carComponent.inject(this)
car.drive()
}
}
Remote.kt
class Remote @Inject constructor() {
fun enableRemote(car: Car) {
Log.d(TAG, "Remote is connected")
}
}
CarComponent.kt
@Component(modules = [WheelsModule::class, PetrolEngineModule::class])
interface CarComponent {
fun getCar(): Car
fun inject(mainActivity: MainActivity)
interface CarComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun horsePower(@Named("horsePower")hp: Int): Builder
// 인스턴스의 타입이 같을 때 이름을 설정하여 주입 하는 것
@BindsInstance
fun engineCapacity(@Named("engineCapacity")cap: Int): Builder
fun build(): CarComponent
}
PetrolEngine.kt
//Named 으로 어떤 것을 주입 받아야 하는지 명확해짐
class PetrolEngine @Inject constructor(
@Named("horsePower") val horsePower: Int,
@Named("engineCapacity") val engineCapacity: Int
) : Engine {
override fun start() {
Log.d(TAG, "Petrol engine started $horsePower hp, $engineCapacity capacity")
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
@Inject
lateinit var car : Car
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val carComponent = DaggerCarComponent
.builder()
.horsePower(150)
.engineCapacity(1400)
.build()
carComponent.inject(this)
car.drive()
}
}
Container에서 제공하는 인스턴스들이 한번 만 생성 되도 되는 것을 부를때마다 계속 생성되어 메모리 누수가 생기는 것을 방지하기 위해서 쓰는 것
Scope를 지정하여 관리하는 것 범위를 결정
Car.kt
const val TAG = "Dagger Car"
class Car @Inject constructor(val wheels: Wheels, val engine: Engine, val driver: Driver) {
fun drive() {
engine.start()
Log.d(TAG, "나는 $driver $this")
}
@Inject
fun connectRemote(remote: Remote) {
remote.enableRemote(this)
}
}
Driver.kt
@Singleton
class Driver @Inject constructor() {}
CarComponent.kt
@Singleton
@Component(modules = [WheelsModule::class, PetrolEngineModule::class])
interface CarComponent {
fun getCar(): Car
fun inject(mainActivity: MainActivity)
@Component.Builder
interface Builder {
@BindsInstance
fun engineCapacity(@Named("engineCapacity")cap: Int): Builder
fun build(): CarComponent
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
@Inject
lateinit var car1 : Car
@Inject
lateinit var car2 : Car
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val carComponent = DaggerCarComponent
.builder()
.horsePower(150)
.engineCapacity(1400)
.build()
carComponent.inject(this)
car1.drive()
car2.drive()
}
}
Dagger2CarApplication.kt
class Dagger2CarApplication: Application() {
lateinit var appComponent: ApplicationComponent
override fun onCreate() {
super.onCreate()
appComponent = DaggerApplicationComponent.create()
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
@Inject
lateinit var car1: Car
@Inject
lateinit var car2: Car
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val carComponent = DaggerCarComponent
.builder()
.horsePower(120)
.engineCapacity(1400)
.getApplicationComponent((applicationContext as Dagger2CarApplication).appComponent)
.build()
carComponent.inject(this)
car1.drive()
car2.drive()
}
}
ApplicationComponent.kt
@Singleton
@Component(modules = [DriveModule::class])
interface ApplicationComponent {
fun getDriver(): Driver
}
CarComponent.kt
@PerActivity
@Component(dependencies = [ApplicationComponent::class] ,
modules = [WheelsModule::class, PetrolEngineModule::class])
interface CarComponent {
fun getCar(): Car
fun inject(mainActivity: MainActivity)
@Component.Builder
interface Builder {
@BindsInstance
fun engineCapacity(@Named("engineCapacity")cap: Int): Builder
fun getApplicationComponent(appComponent: ApplicationComponent): Builder
fun build(): CarComponent
}
}
Car.kt
@PerActivity
class Car @Inject constructor(val wheels: Wheels, val engine: Engine, val driver: Driver) {
fun drive() {
engine.start()
Log.d(TAG, "나는 $driver $this")
}
@Inject
fun connectRemote(remote: Remote) {
remote.enableRemote(this)
}
}
Driver.kt
class Driver {}
PerActivity.kt
//커스텀Scope 만들기
/*Container에서 제공하는 인스턴스들이 한번만 생성되도 되는데 호출 될때마다 생성 되며 불필요한 메로리 사용이 생김
이를 해결하기 위해 하는것이 @Scope
한번 생성된 객체가 재사용되는 범위를 관리*/
// Scope는 객체가 재사용되는 범위만을 가리킬 뿐
@Scope
annotation class PerActivity {}
DriveModule.kt
@Module
class DriveModule {
companion object {
@JvmStatic
@Singleton
@Provides
fun drive(): Driver = Driver()
}
}
dagger2에서는 SubComponent
를 생성할 수 있으며 SubComponent
는 말 그대로 부모 Component
가 있는 자식 Component
Inject
로 의존성 주입을 요청받으면 SubComponent
에서 먼저 의존성을 검색하고, 없으면 부모로 올라가면서 검색을 하고 SubComponent
는 Component
와 달리 코드 생성은 부모 Component
에서 이루어 짐
MainActivity.kt
class MainActivity : AppCompatActivity() {
@Inject
lateinit var car1: Car
@Inject
lateinit var car2: Car
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
(applicationContext as Dagger2CarApplication).appComponent
.getCarComponentBuilder()
.horsePower(120)
.engineCapacity(1400)
.build()
.inject(this)
car1.drive()
car2.drive()
}
}
ApplicationComponent.kt
@Singleton
@Component(modules = [DriveModule::class])
interface ApplicationComponent {
fun getCarComponent(dieselPetrolEngineModule: DieselPetrolEngineModule): CarComponent
fun getCarComponentBuilder(): CarComponent.Builder
}
CarComponent.kt
@PerActivity
//여러 Component들을 쓸수 있게 의존 관게를 하고 상위에 프로바이더를 쓸 수 있도록 하는 것
@Subcomponent(modules = [WheelsModule::class, PetrolEngineModule::class])
interface CarComponent {
fun getCar(): Car
fun inject(mainActivity: MainActivity)
@Subcomponent.Builder
interface Builder {
@BindsInstance
fun horsePower(@Named("horsePower")hp: Int): Builder
@BindsInstance
fun engineCapacity(@Named("engineCapacity")cap: Int): Builder
fun build(): CarComponent
}
}
@Binds
는 @Provides
의 그냥 특수한 형태 이며 역활은 같다Module
에서 제공 하는 클래스의 구현체(interface 구현체, 객체)를 바인딩하고자 할 때 사용@Provider
를 넣으려면 Companion object
에만 가능Car.kt
const val TAG = "Dagger Car"
class Car @Inject constructor(val wheels: Wheels, val engine: Engine) {
fun drive() {
engine.start()
Log.d(TAG, "나는 드라이버")
}
@Inject
fun connectRemote(remote: Remote) {
remote.enableRemote(this)
}
}
CarComponent.kt
@Component(modules = [WheelsModule::class, PetrolEngineModule::class])//, DieselPetrolEngineModule::class])
interface CarComponent {
fun getCar() : Car
fun inject(mainActivity: MainActivity)
}
Engine.kt
interface Engine{
fun start()
}
PetrolEngine.kt
class PetrolEngine @Inject constructor(): Engine {
override fun start() {
Log.d(TAG, "Petrol engine started")
}
}
DieselPetrolEngineModule.kt
@Module
abstract class DieselPetrolEngineModule {
@Binds
abstract fun bindEngine(engine: DieselEngine): Engine
}
PetrolEngineModule.kt
//@Binds는 @Provides의 특수한 형태일 뿐 같은 역활을 함
// 추상 클래스와 추상 메서드에만 유효하며 반드시 하나의 매개 변수만을 가져와야 함
@Module
abstract class PetrolEngineModule {
@Binds
abstract fun bindEngine(engine: PetrolEngine): Engine
}
WheelsModule.kt
@Module
class WheelsModule {
@Module
companion object {
@JvmStatic
@Provides
fun provideRims() = Rims()
@JvmStatic
@Provides
fun provideTires(): Tires {
val tires = Tires()
tires.inflateTires()
return tires
}
@JvmStatic
@Provides
fun provideWheels(rims: Rims, tires: Tires) = Wheels(rims, tires)
}
}
Component
내부에는 자체적으로 Builder
가 들어 있음
하지만 이러한 Builder
를 설정하기 하기위해 @BuindsInstance
를 사용하여 Builder
를 설정하는 것
@BuindsInstance
에는 Component
빌더 안에 설정 되면 @BuindsInstance
에 의해 제공된 모든 모듈에 제공이 가능 한것
MainActivity.kt
class MainActivity : AppCompatActivity() {
@Inject
lateinit var car : Car
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val carComponent = DaggerCarComponent
.builder()
.horsePower(150)
.build()
carComponent.inject(this)
car.drive()
}
}
CarComponent.kt
@Component(modules = [WheelsModule::class, PetrolEngineModule::class])
interface CarComponent {
fun getCar() : Car
fun inject(mainActivity: MainActivity)
// 별도의 Builder를 설정하지 않아도 Component내부에 Builder가 만들어져 있음
// Builder를 따로 설정하기 위해서 Component.Builder를 선언
// 기존 빌더와 달라진 점은
// -> 빌더 내부에 메서드가 들어가고 해당 메서드를 통해 값을 세팅
@Component.Builder
interface Builder{
// 특정 인스턴스를 Provide하는 @BindsInstance가 Component이 빌더안에 설정되면 해당 Component는
// BindsInstance에 의해 제공된 Component 및 그 하위의 모든 모듈에 Provide가 가능
// 그래서 PetrolEngineModule에 없어도 제공이 가능하게 된 것
//TODO 그냥 Provider의 특수한 형태 @Bind 와 관련된 Annotation은 모두 Provider의 그냥 특수한 형태라고 함
@BindsInstance
fun horsePower(hp: Int): Builder
fun build(): CarComponent
}
}
PetrolEngine.kt
class PetrolEngine @Inject constructor(val horsePower: Int): Engine {
override fun start() {
Log.d(TAG, "Petrol engine started $horsePower hp")
}
}
참고
https://mimisongsong.tistory.com/34?category=1231081
https://qiita.com/iTakahiro/items/c9203f5ad886b7ddae2f
https://developer88.tistory.com/173
https://jaejong.tistory.com/125
https://kotlinworld.com/111