Dagger2
Dagger2は、オブジェクト間の依存関係を管理し、コードを自動生成するツールです。
依存性注入に必要な要素
- Component :
クラスのインスタンスを集める役割を果たし、各インスタンスはModule単位で宣言して提供します。
- Module :
@Provider クラスのインスタンスを集める役割を果たし、各インスタンスはModule単位で宣言して提供します。
- Provider :
提供するオブジェクトのインスタンスメソッドに付けるもので、クラスに何を提供するかを示します。
Dagger2で使用される概念
- Constructor
- Provision
- Member Injection
- Named Injection
- Scope
- SubComponent
- Binds
- BindsInstance
@Inject Constructor
-
フィールド、コンストラクター、メソッドに付けて、
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)
// DaggerCarComponentのcreate()メソッドはCarComponentインターフェースを実装するオブジェクトを返します。
// Daggerを通じてオブジェクトが作成され、Carの構成要素が作成されます。
val carComponent : CarComponent = DaggerCarComponent.create()
val car: Car = carComponent.getCar()
car.drive()
}
}
Provision
-
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
-
Member Injection
は、インスタンスを作成した後、Provider
によって提供されるインスタンスをメソッドのパラメータとして注入する方法があります。 -
Field Injection
とMethod Injection
の二つの方法で注入されます。 -
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
-> メソッドにパラメータを入力する必要がある値にProviderが提供するインスタンスを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")
}
}
Named Injection
- Providerから提供されるインスタンスのタイプが同じ場合、どのタイプのインスタンスを注入して使用するかを区別して決定することです。
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()
}
}
Scope
-
Containerから提供されるインスタンスが一度だけ作成されても良いものを、呼び出すたびに継続的に作成され、メモリ漏れが発生するのを防ぐために使用されるものです。
-
Singleton(シングルトン)やCustom Scope(カスタムスコープ)などがあります。
1. Singleton
- オブジェクトが再利用される範囲を設定するためのようなオブジェクトを使用すること
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()
}
}
2. CustomScope
- annotation classを作って使用 文字通りカスタムすること
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
//カスタムスコープの作成
/* Containerから提供されるインスタンスが一度作成されれば十分な場合でも、呼び出すたびに作成され、無駄なメモリ使用が発生します。
これを解決するために@Scopeを使用します。
一度作成されたオブジェクトが再利用される範囲を管理します。 */
// Scopeはオブジェクトが再利用される範囲を指します。
@Scope
annotation class PerActivity {}
DriveModule.kt
@Module
class DriveModule {
companion object {
@JvmStatic
@Singleton
@Provides
fun drive(): Driver = Driver()
}
}
SubComponent
-
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を使用できるように依存関係を持ち、上位のProviderを使用できるようにします。
@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
-
@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)
}
}
BindsInstance
-
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{
// 特定のインスタンスを提供する@BindsInstanceがComponentのビルダー内に設定されると、そのComponentは
// BindsInstanceによって提供されたComponentおよびそのすべての下位モジュールに提供することが可能です。
// そのため、PetrolEngineModuleになくても提供が可能になります。
// TODO ただし、Providerの特別な形式である@Bindおよび関連するアノテーションは、すべて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