白眼鏡のblog

新しく得た知見を備忘録的に書き連ねていく

Dagger Androidの導入方法

AndroidのDIコンテナとしてメジャーなDagger
そのAndroid用の拡張版であるDagger-Androidの導入方法を今更ながら、内部の実装に踏み込まず、純粋にプロジェクトに導入するにはどうするべきかというスコープでまとめてみます。

Daggerライブラリのインポート

Daggerを使うために必要なライブラリをプロジェクトに読み込みます。
build.gradle(module)

plugins {  
    id("kotlin-kapt")
}

......
dependencies {
    ......
    implementation("com.google.dagger:dagger:2.25.2")
    implementation("com.google.dagger:dagger-android:2.25.2")
    implementation("com.google.dagger:dagger-android-support:2.25.2")
    kapt("com.google.dagger:dagger-compiler:2.25.2")
    kapt("com.google.dagger:dagger-android-processor:2.25.2")
    ......
}

Daggerを使ってInjectしたいクラスを作成

今回はHelloというクラスを対象のActivityやFragmentなどにInjectしたいとします。
Helloクラスは次のようになっています。

class Hello(private val name: String = "Dagger") {
    fun echo() = println("Hello $name")
}

このHelloクラスを普通にインスタンスを作成して呼び出す場合、次のようになります。

val hello = Hello()
hello.echo()
// (Hello Dagger) と出力される。

このHelloクラスをDaggerを使って、次のように呼び出せるようにしていきます。

@Inject
lateinit var hello: Hello

hello.echo()
// (Hello Dagger) と出力される。

Helloクラスを提供するModuleを作成

まず、Helloクラスを任意の場所にInjectできるようにするため、HelloModule(名前は任意)を作成します。

@Module
object HelloModule {
    @Singleton
    @Provides
    fun provideHello() = Hello()
}

ActivityModuleの作成

HelloクラスをInjectできるようにActivityについて宣言しておきます。

@Module
abstract class ActivityModule {
    @ContributesAndroidInjector
    internal abstract fun contributeMainActivity(): MainActivity()
}

AppComponentの作成

上記のHelloModuleとActivityModuleを参照するAppComponentを作成します。

@Singleton
@Component(
    module = [
        AndroidInjectionModule::class, // Dagger-Androidを使用する場合は必須
        ActivityModule::class,
        HelloModule::class
    ]
)
interface AppComponent : AndroidInjector<ApplicationWrapper> 

Applicationクラスの作成

ここまでで作成したComponentとModuleをアプリから参照できるようにしていきます。
次のようなApplicationクラスを継承したクラスを作成し、Manifestに登録します。

class ApplicationWrapper : Application(), HasAndroidInjector {

    @Inject
    lateinit var androidInjector: DispatchingAndroidInjector<Any>

    override fun androidInjector(): AndroidInjector<Any> {
        DaggerAppComponent
            .create()
            .inject(this)
        return androidInjector
    }
}

ここまでの作業で、Dagger-Androidを使用する下準備は完了です。

ActivityへInjectする。

それでは、MainActivityのメンバ変数にHelloクラスをInjectしていきます。

class MainActivity : AppCompatActivity(), HasAndroidInjector // 必須
{

    @Inject
    lateinit var androidInjector: DispatchingAndroidInjector<Any> // 必須

    @Inject
    lateinit var hello: Hello // ここへHelloクラスをInjectする

    override fun onCreate(savedInstanceState: Bundle?) {
        AndroidInjection.inject(this) // Inject
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    // ↓追記
    override fun androidInjector(): AndroidInjector<Any> {
        return androidInjector
    }
}

これで、helloにHelloクラスがinjectされます。

FragmentへInjectする

Fragmentに対してInjectする場合ですが、こちらについてもまずはFragmentのModuleの作成を行います。

@Module
abstract class FragmentModule {
    @ContributesAndroidInjector
    internal abstract fun contributeMainFragment(): MainFragment
}

これでMainFragmentの定義ができました。
これをAppComponentに追加します。

@Singleton
@Component(
    module = [
        AndroidInjectionModule::class,
        ActivityModule::class,
        FragmentModule::class, // 追加
        HelloModule::class
    ]
)
interface AppComponent : AndroidInjector<ApplicationWrapper> 

あとは、Fragmentで呼び出すだけです。

class MainFragment : Fragment() {
    companion object {
        @Suppress("unused")
        private val TAG: String = MainFragment::class.java.simpleName
    }

    private lateinit var binding: FragmentMainBinding

    @Inject
    lateinit var hello: Hello

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false)

        return binding.root
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        AndroidSupportInjection.inject(this) // 追記
    }
}

これだけで使用することができます。 これで、基本的な呼び出し方は完了です。

Contextが欲しい場合

例えば次のようなクラスがあるとします。

class Colors(private val context: Context) {
    fun getWhite(): Int =
        ContextCompat.getColor(context, android.R.color.white)
    
    fun getBlack(): Int =
        ContextCompat.getColor(context, android.R.color.black)
}

このColorsクラスはコンストラクタの引数で渡されたContextを元にColorの値を返却します。
このクラスをDaggerを使ってInjectする場合、Contextを提供するProvideメソッドを作成する必要があります。
Contextを提供できるようにするため、まずはAppComponentを編集します。

interface AppComponent : AndroidInjector<ApplicationWrapper> {
// 追加
    @Component.Factory
    interface Factory : AndroidInjector.Factory<ApplicationWrapper>
// ここまで
}

この変更を行うとApplicationを継承したクラスがビルドできなくなるため、次のような修正を加えます。

    override fun androidInjector(): AndroidInjector<Any> {
        DaggerAppComponent
            .factory()  // 追記
            .create(this)  // 引数にthisを設定
            .inject(this)
        return androidInjector
    }

ここまでできたら、あとはColorsクラスとContextをProvideするメソッドを作成します。

@Module
object AppModule {
    @Singleton
    @Provides
    fun provideContext(applicationWrapper: ApplicationWrapper): Context = applicationWrapper.applicationContext

    @Singleton
    @Provides
    fun provideColors(context: Context) = Colors(context)
}

これで、準備完了です。 呼び出したいActivityやFragmentでInjectしてあげることでColorsクラスを使用することができます。

以上