はじめに
AndroidアプリにDaggerを導入した際の手順と
自分がDaggerを学び始めた際に、とっかかりとして欲しかったなぁと思う情報をまとめます。
今回の記事は自分自身がDagger初心者ということもあり、初歩的な内容となっています。
その分、同じDagger初心者の方に分かりやすく書けたらいいなと思いこの記事を書き始めました。
Daggerは、JavaやKotlinでDI(Dependency Injection)を行うことをサポートするライブラリです。
DIはたびたび「依存性の注入」と訳されますが、この訳がDIを意味不明なものにしていると思っています。
ネットで検索していると「DI = 依存オブジェクトの注入」と訳されている方がおり、
とてもスッキリしたので本記事でも「DI = 依存オブジェクトの注入」で進めようと思います。
Dependency Injectionを「依存性の注入」と訳すのは非常に悪い誤訳
Dependency Injection
例えば、ViewModelでSharedPreferencesを扱う必要があるとき、
SharedPreferencesのラッパークラスをSharedPreferenceManagerとして定義していたとすると、
単純に実装すると下記の例のようになると思います。
class SampleViewModel (
app: Application
) : AndroidViewModel(app) {
private val prefManager: SharedPreferenceManager =
SharedPreferenceManager(app.applicationContext)
}
このようにすると、
ViewModelが本来知る必要のないSharedPreferenceManagerのインスタンス生成方法を知る必要が出てきます。
ここで、ViewModelで必要なオブジェクト(依存オブジェクト = Dependency)を外部から注入(Injection)するのがDependency Injectionです。
class SampleViewModel (
private val prefManager: SharedPreferenceManager
) : ViewModel()
ただし、このように単純にコンストラクタで依存オブジェクトを注入しても
結局、呼び出し元(この場合はActivityやFragment)でも知る必要のない
SharedPreferenceManagerのインスタンス生成方法を知る必要が出てきます。
このジレンマを簡単に解消してくれるのがDaggerです。
導入
開発言語はKotlinです。
まず、app/build.gradleにDaggerへの依存関係を追加します。
dependencies {
def dagger_version = "2.26"
// dagger
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
}
画面上部の"Sync Now"をクリックしてDaggerへの依存関係を解決します。
アノテーション
Daggerでは、変数やクラス、コンストラクタ等にアノテーションをつけることで
クラス同士の依存関係を定義できたり、インスタンスのスコープを明確にしたりすることができます。
ここでは、Daggerを導入することで利用できる下記のアノテーションについて
簡単な説明を記載したのちに、具体的な実装例を記載します。
- Component
- Module
- Inject
- BindsInstance
- Provide
- Singleton
Component
Componentアノテーションが付与されたインターフェースは、オブジェクト注入屋さんです。
オブジェクトを注入して欲しいと宣言されている(Injectアノテーションが付与されている)変数やコンストラクタにオブジェクトを注入してくれます。
Module
Moduleはインスタンス生成屋さんです。
Componentが見つけた、Injectアノテーションが付与された変数のインスタンスを生成します。
各クラスで知る必要のなかったインスタンスの生成方法は、Moduleが全て知ることになります。
Inject
クラスのコンストラクタやフィールドに対してInjectアノテーションを付与することで
Componentがそれを見つけて、クラスが依存するオブジェクトを注入してくれます。
BindsInstance
Componentに対してインスタンスをバインドするアノテーションです。
主に、開発者は生成方法を知らない(知る必要がない)けれど、アプリ内で必要なインスタンスがバインドされます。
Androidアプリケーションの場合、Contextをバインドすることがほとんどかなと思っています。
Provide
Module内で、インスタンスの生成方法を記載する際に利用するアノテーションです。
Moduleをインスタンス生成屋さんとすると、Provideはレシピのようなものです。
Singleton
インスタンスをシングルトンで生成したい時に付与するアノテーションです。
実装例
まずは、オブジェクト注入屋さんであるComponentを定義します。
@Singleton
@Component
interface AppComponent {
@Component.Factory
interface Factory {
fun create(@BindsInstance context: Context): AppComponent
}
}
Componentには、Component自体を生成するFactoryインターフェースを宣言します。
Factoryインターフェースには、Componentを生成することを示すため
@Component.Factoryアノテーションを付与します。
createメソッドの引数として受け取るcontextには、BindsInstanceアノテーションを付与して
contextインスタンスをComponentにバインドします。
次に、インスタンス生成屋さんであるModuleを定義します。
@Module
class AppModule {
@Singleton
@Provides
fun provideSharedPreferenceManager(
context: Context
): SharedPreferenceManager {
return SharedPreferenceManager(context)
}
}
ここでは、SharedPreferenceを管理する自作クラス(SharedPreferenceManager)の
インスタンス生成方法をModuleに記載し、
さらにSingletonアノテーションを付与することで、SharedPreferenceManagerをシングルトンインスタンスとして提供するよう定義しています。
そしてComponentに戻り、インスタンス生成屋さん(Module)の存在を伝えます。
@Singleton
@Component(
modules = [AppModule::class]
)
interface AppComponent {
@Component.Factory
interface Factory {
fun create(@BindsInstance context: Context): AppComponent
}
}
ここまで進めば、あとはどこに対してDIを適用するかを決めるだけです。
今回はアプリ全体に適用させたいので、Applicationクラスを継承したクラスにComponentを持たせてアプリ全体にDIを適用させます。
class MyApplication : Application() {
val appComponent: AppComponent by lazy {
DaggerAppComponent.factory().create(applicationContext)
}
}
これにより、Componentはアプリ内全域のInjectアノテーションが付与された変数に対して
Moduleで生成されたインスタンスを注入することができるようになります。
下記は、SharedPreferenceManagerに依存するSampleViewModelクラスを定義する際の例です。
class SampleViewModel @Inject constructor(
private val preferenceManager: SharedPreferenceManager
) : ViewModel()
最後に
Daggerの概念が分かりにくく、導入に苦労しましたが、
導入してみるとアプリの全体像がかなり把握しやすくなり、インスタンスの取得が簡単にできるのでコードの可読性も上がりました。
今回紹介したのはDaggerのほんの一部の機能で、私自身もまだまだ勉強中です。
誤記等ありましたらコメントでご指摘頂けますと幸いです!