LoginSignup
1
2

Dagger2とは?

Last updated at Posted at 2024-01-08

DALL·E 2023-12-11 16.53.45 - A fun and approachable illustration depicting the concept of dependency injection in Android development using Dagger 2. The image should include a ch.png

Dagger2

Dagger2は、オブジェクト間の依存関係を管理し、コードを自動生成するツールです。

依存性注入に必要な要素

  • Component :
    クラスのインスタンスを集める役割を果たし、各インスタンスはModule単位で宣言して提供します。

  • Module :
    @Provider クラスのインスタンスを集める役割を果たし、各インスタンスはModule単位で宣言して提供します。

  • Provider :
    提供するオブジェクトのインスタンスメソッドに付けるもので、クラスに何を提供するかを示します。

Dagger2で使用される概念

  • Constructor
  • Provision
  • Member Injection
  • Named Injection
  • Scope
  • SubComponent
  • Binds
  • BindsInstance

@Inject Constructor

  • フィールド、コンストラクター、メソッドに付けて、 Componentから依存性オブジェクトを注入要求するアノテーションです。annotation

  • @Injectで依存性注入を要求すると、関連する ComponentModuleからオブジェクトを生成して渡し、
    Component@Inject annotationアノテーションを依存性注入するメンバ変数とコンストラクターに付けることで、DI対象を確認できます。

  • インスタンスの生成がクラス内ではなく、Componentによって行われます。

  • 基本的にDaggerは要求されたType(資料型)に合わせて ComponentModuleからオブジェクトを生成して注入する

ただし、

  • InterfaceActivityFragmentなど 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 InjectionMethod 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はまず自身で依存性を探し、なければ親に上がって探します。SubComponentComponentと異なり、コード生成は親 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 を設定することです。

  • @BuindsInstanceComponentのビルダー内に設定されると、 @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

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2