v2.25のときに個人的に調べたものです。内容には間違いが含まれている場合があります。
概要
- 依存性の注入(DI)とは処理に必要なオブジェクトをロジックの外から渡してやるテクニックのこと
- 処理側はインスタンスの生成方法を知らずに済むので、その間の結合度が低下する
- DaggerはDIを実現するためのフレームワーク
- インスタンスの生成と渡しを自動化する
- 依存関係をコンパイル時に解決するため、実行時コストが小さい
使い方
- 依存関係のグラフを定義する
- グラフに対応する実装をビルド時に生成する
- 生成された実装を使ってDIを行う
依存関係の定義
受け取る側
- あなたが定義するクラスに対して、依存性を注入したい箇所に
@Injectを付ける- コンストラクタに付けると、コンストラクタ引数を介して間接的に注入される
- メンバ変数に付けると、直接代入することで注入される
- そのメンバ変数はDaggerから見えるようにする必要がある
注入の遅延
Provider<T>とLazy<T>はgetメソッドを呼び出したときにはじめてTを注入するLazyは最初の呼び出しで注入されたインスタンスをキャッシュする
提供する側
- 依存関係を満たすためのメソッドを集めたクラスに
@Moduleを付ける- メソッドには生成したい実装に対応した注釈を付ける
- 慣例として、モジュール名は接尾辞
Moduleを持つ
Provides
- インスタンスを提供するメソッドに
@Providesを付ける- そのメソッドは戻り値の型が要求されるたびに呼び出される
- 引数があれば、メソッドが呼び出されるときに注入される
- 慣例として、関数名は
provideという動詞を使う
Binds
@Bindsの付いた抽象メソッドはDaggerによって実装が生成される- その引数はただ1つのみ
- その引数の型は戻り値の型に代入可能でなければならない
- その実装は戻り値の型が要求されるたびに注入された引数をそのまま返す
グラフの構築
コンポーネント
- 指定の形式で記述されたインターフェイスに
@Componentを付けるmodules要素に列挙されたモジュールはDaggerが実装を生成するのに使われる
- 慣例として、コンポーネント名は接尾辞
Componentを持つ - Daggerはそのコンポーネントに対して接頭辞
Daggerを持つ実装クラスを生成する
Provisionメソッド
- DI処理によって生成されたインスタンスを取得するメソッド
- そのメソッドは引数を持たない
- 戻り値は
Tのほか、Provider<T>やLazy<T>にもできる
Member-injectionメソッド
- インスタンスのメンバ変数に依存性を注入するためのメソッド
- そのメソッドは以下の形式で宣言される
- 引数を1つだけ持ち、何も返さない
- 引数を1つだけ持ち、引数の値を返す
- 引数を持たず、
MemberInjector<T>を返す- この場合は
MemberInjector.injectMembers(T)の呼び出しによって注入される
- この場合は
- そのメソッドは以下の形式で宣言される
ファクトリ
- 指定の形式で記述されたインターフェイスに
@Component.Factory付ける- その定義はコンポーネントの内部に記述する
- その本体はただ1つのメソッドを持つ
- そのメソッドはコンポーネント型(の派生型)を戻り値とする
- その引数は以下のインスタンスを渡すよう宣言される
- 必須:非抽象クラスのモジュール
- 必須:依存関係にあるコンポーネント
- 任意:
@BindsInstanceを付けた引数- その型に対して注入されるインスタンスをセットする
- Daggerが生成する実装はこのファクトリのインスタンスを返す静的メソッド
factory()を定義する
サブコンポーネント
DI処理の組み込み
- ビルド時に生成される接頭辞
Daggerを持つコンポーネントの実装クラスを用いる- コンポーネントのファクトリを用いてコンポーネントを生成する
- コンポーネントのメソッドを用いてインスタンスを生成したり、依存性を注入したりする
Android対応
- DaggerはAndroidアプリでDIを行うための仕組みを備えている
@ContributesAndroidInjectorはそのモジュールのサブコンポーネントとしてのAndroidInjectorを提供するメソッドを生成するDaggerApplication、DaggerActivity、DaggerFragmentなどはthisへの注入を行うために継承される- その基底クラスは
HasAndroidInjectorを実装し、onCreateやonAttachで依存性の注入を行う
- その基底クラスは
Applicationの用意
- あなたのApplicationに
DaggerApplicationを継承させる- Applicationのコンポーネントを保持し、
applicationInjector()でそのインスタンスを返す
- Applicationのコンポーネントを保持し、
- あなたのApplicationのコンポーネントに
AndroidInjector<T>を継承させる - あなたのApplicationのコンポーネントに
AndroidInjectionModuleをインストールする - あなたのApplicationのコンポーネントファクトリに
AndroidInjector.Factory<T>を継承させる
Activityの追加
- そのActivityに
DaggerActivityを継承させる - そのActivityに対する
@ContributesAndroidInjectorメソッドを宣言したモジュールをApplicationのコンポーネントにincludeする - そのActivityのコンポーネントにincludeしたいモジュールを
@ContributesAndroidInjectorのmodules要素に列挙する
Fragmentの追加
- あなたのFragmentに
DaggerFragmentを継承させる - そのFragmentに対する
@ContributesAndroidInjectorメソッドを宣言したモジュールをActivityのコンポーネントにincludeする- または、Activityに対する
@ContributesAndroidInjectorのmodules要素に加える
- または、Activityに対する
- そのFragmentのコンポーネントにincludeしたいモジュールを
@ContributesAndroidInjectorのmodules要素に列挙する
ViewModelへ注入したい場合
ViewModelはActivityやFragmentのライフサイクルで管理される方が良い?ViewModelProviderを介して生成する必要がある
- 方法1:
Provider<T>からgetされたViewModelのインスタンスを返すViewModelProvider.Factoryの実装を注入する
Scope
- Daggerが管理するオブジェクトの寿命はコンポーネントに紐付いている
- コンポーネントにスコープ注釈を付けると、その要素が同一スコープのみで構成されるように強制できる?
- コンポーネント内に寿命の異なるオブジェクトが混入するとコンパイルエラーにできる?
- スコープの付いたメソッドから提供されるインスタンスはコンポーネントによって保持される
- インスタンスをキャッシュする
DoubleCheck型を用いるようになる
- インスタンスをキャッシュする
- スコープ注釈として
@Singletonが定義されている
サンプルコード
class Foo
class Bar @Inject constructor(
private val foo: Foo
)
class Baz {
@Inject
internal lateinit var bar: Bar
init {
DaggerFooBarBazComponent
.factory()
.create()
.injectBaz(this)
}
}
@Module
class FooBarBazModule {
@Provides
fun provideFoo(): Foo {
return Foo()
}
}
@Component(modules = [FooBarBazModule::class])
interface FooBarBazComponent {
// Provisionメソッド
fun getFoo(): Foo
fun getBarProvider(): Provider<Bar>
fun getLazyBaz(): Lazy<Baz>
// Member-injectionメソッド
fun injectBaz(baz: Baz)
fun injectAndReturnBaz(baz: Baz): Baz
fun getBazInjector(): MembersInjector<Baz>
// コンポーネントのファクトリ
@Component.Factory
interface Factory {
fun create(): FooBarBazComponent
}
}