|
| 1 | +<img src="https://github.com/YukiMatsumura/AndroidDaggerSample/blob/master/art/android_robot.png?raw=true" align="right" /> |
| 2 | + |
| 3 | +# DAGGER 2.11-rc1 SAMPLE |
| 4 | + |
| 5 | +### 🗡 Dagger 2.11 |
| 6 | + |
| 7 | +Dagger2.10で[`dagger.android`モジュールがリリース](https://google.github.io/dagger/android)されました. |
| 8 | +本稿ではDagger2.10と2.11でリリースされた`dagger.android`モジュールの使い方について簡単に紹介したいと思います. |
| 9 | + |
| 10 | +本題へ入る前に, Dagger2.11では当然, 歴代のバージョンで追加されてきた機能を土台にしています. |
| 11 | +Daggerを触ったことがない人は [Android: Dagger2](http://yuki312.blogspot.jp/2016/03/android-dagger2.html) を. |
| 12 | +Subcomponentを使ったことがない人は[Android: Dagger2 - Subcomponent vs. dependencies](http://yuki312.blogspot.jp/2016/02/android-dagger2-subcomponent-vs.html)を. |
| 13 | +マルチバインディングを使ったことがない人は[Dagger2. MultibindingでComponentを綺麗に仕上げる](http://yuki312.blogspot.jp/2017/02/dagger2-multibindingcomponent.html)を一度読んでから本稿に戻ってくると理解しやすいと思います. |
| 14 | + |
| 15 | +Dagger(Dependency Injection)を最大限に活かせるのは, 依存オブジェクトをDagger自身が生成して, 依存性を満たすようにデザインすることでしょう. しかし, AndroidはActivityやFragmentといったOSが生成・管理するオブジェクトがあり, Daggerが全てを生成・管理することができません. |
| 16 | +そうした場合, 次のようにフィールドインジェクションを使って依存性を満たすことになります. |
| 17 | + |
| 18 | +```java |
| 19 | +public class MainActivity extends Activity { |
| 20 | + @Inject Hoge hoge; |
| 21 | + |
| 22 | + @Override |
| 23 | + public void onCreate(Bundle savedInstanceState) { |
| 24 | + super.onCreate(savedInstanceState); |
| 25 | + // 必ず最初に実行すること! |
| 26 | + ((App) getContext().getApplicationContext()) |
| 27 | + .getApplicationComponent() |
| 28 | + .newActivityComponentBuilder() |
| 29 | + .activity(this) |
| 30 | + .build() |
| 31 | + .inject(this); |
| 32 | + // ... |
| 33 | + } |
| 34 | +} |
| 35 | +``` |
| 36 | + |
| 37 | +これにはいくつかの問題があります. |
| 38 | + |
| 39 | + 1. まず, ActivityやFragment, Service, ContentProviderといったOS管理のクラスへインジェクションする数だけコピペコードが出来上がり, メンテナンス性を悪くします. |
| 40 | + 2. そしてなにより, クラスが依存性を注入するオブジェクト(ComponentやModules)のことについてそれぞれのクラスが知っている必要があるため, Dependency Injectionのコア原則を破っています. |
| 41 | + |
| 42 | +今回紹介する`dagger.android`モジュールを導入すると, これらの問題を解決することができます. |
| 43 | + |
| 44 | +``` |
| 45 | +NOTE: |
| 46 | +android.daggerモジュールはまだBetaバージョンのため今後変更される可能性があります. |
| 47 | +今でもクラス名がリネームされるなどしているため, 他でコードを参考にされる場合はdaggerのバージョンに注意する必要があります. |
| 48 | +
|
| 49 | +本稿では現時点で最新のリリースバージョンDagger2.11-rc1を対象にしています. |
| 50 | +StableのDagger2.10からの変更点もありますので, Dagger2.10を使う場合は変更点にご注意ください. |
| 51 | +
|
| 52 | +Dagger2.10 -> 2.11の変更点: |
| 53 | + - New API: @ContributesAndroidInjector simplifies the usage of dagger.android |
| 54 | + - All HasDispatching*Injectors are renamed to Has*Injector. They also return an AndroidInjector instead of a DispatchingAndroidInjector |
| 55 | + - Added DaggerApplication and DaggerContentProvider |
| 56 | +
|
| 57 | +リネーム情報はGitHubのリリースページに記載されています. |
| 58 | +https://github.com/google/dagger/releases |
| 59 | +``` |
| 60 | + |
| 61 | + |
| 62 | +### 依存ライブラリの追加 |
| 63 | + |
| 64 | +まずはDagger2.11のライブラリを追加しないとはじまりません. |
| 65 | +build.gradleのdependenciesに次のライブラリを追加します. |
| 66 | + |
| 67 | +``` |
| 68 | + // Core dependencies |
| 69 | + compile 'com.google.dagger:dagger:2.11-rc1' |
| 70 | + annotationProcessor 'com.google.dagger:dagger-compiler:2.11-rc1' |
| 71 | +
|
| 72 | + // Android dependencies |
| 73 | + compile 'com.google.dagger:dagger-android:2.11-rc1' |
| 74 | + annotationProcessor 'com.google.dagger:dagger-android-processor:2.11-rc1' |
| 75 | +
|
| 76 | + // Require if use android support libs. |
| 77 | + compile 'com.google.dagger:dagger-android-support:2.11-rc1' |
| 78 | +``` |
| 79 | + |
| 80 | +`dagger-android-*`なモジュールがDaggerのAndroid拡張です. |
| 81 | +プロジェクトでサポートライブラリを使用している場合は`dagger-android-support`も必要です. |
| 82 | + |
| 83 | +余談ですが, 手元の環境ではfindbugsのdependencyでコンフリクトが起きたので, 合わせて解消しています. |
| 84 | + |
| 85 | +``` |
| 86 | +エラー: |
| 87 | +Error:Conflict with dependency 'com.google.code.findbugs:jsr305' in project ':app'. Resolved versions for app (3.0.1) and test app (2.0.1) differ. See http://g.co/androidstudio/app-test-app-conflict for details. |
| 88 | +
|
| 89 | +解決: espresso-coreの依存モジュールからjsr305をexcludeしておく |
| 90 | + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { |
| 91 | + exclude group: 'com.android.support', module: 'support-annotations' |
| 92 | + exclude group: 'com.google.code.findbugs', module: 'jsr305' |
| 93 | + }) |
| 94 | +``` |
| 95 | + |
| 96 | +Daggerのライブラリを取得したらComponent, Moduleを作成していきましょう. |
| 97 | + |
| 98 | + |
| 99 | + |
| 100 | +### ActivityのComponent/Module作成 |
| 101 | + |
| 102 | +順を追って必要なオブジェクトを作って行きます. まずはMainActivityに紐づくMainComponentの定義から. |
| 103 | + |
| 104 | +MainComponentはこのあと作るアプリケーションコンポーネントのサブコンポーネントとして定義するので`@Subcomponent`アノテーションをつけます. |
| 105 | +さらに, コンポーネントビルダー`@Subcomponent.Builder`を同じく宣言します. |
| 106 | + |
| 107 | +``` |
| 108 | +package com.yuki312.androiddaggersample; |
| 109 | +
|
| 110 | +import dagger.Subcomponent; |
| 111 | +import dagger.android.AndroidInjector; |
| 112 | +
|
| 113 | +@Subcomponent |
| 114 | +public interface MainComponent extends AndroidInjector<MainActivity> { |
| 115 | + @Subcomponent.Builder |
| 116 | + abstract class Builder extends AndroidInjector.Builder<MainActivity> { |
| 117 | + } |
| 118 | +} |
| 119 | +``` |
| 120 | + |
| 121 | +MainComponentには`AndroidInjector`インタフェースを継承させます. |
| 122 | +`AndroidInjector`はAndroidのコアコンポーネント(Activity, Fragment, Service, BroadcastReceiver, ContentProvider)に依存性を注入するメソッド`inject(T)`を定義したインタフェースです. |
| 123 | + |
| 124 | +このインタフェースは■■■ |
| 125 | + |
| 126 | +次にMainModuleを定義します. |
| 127 | + |
| 128 | +``` |
| 129 | +import android.app.Activity; |
| 130 | +import dagger.Binds; |
| 131 | +import dagger.Module; |
| 132 | +import dagger.android.ActivityKey; |
| 133 | +import dagger.android.AndroidInjector; |
| 134 | +import dagger.multibindings.IntoMap; |
| 135 | +
|
| 136 | +@Module |
| 137 | +public abstract class MainModule { |
| 138 | +
|
| 139 | + @Binds @IntoMap @ActivityKey(MainActivity.class) |
| 140 | + abstract AndroidInjector.Factory<? extends Activity> bindInjectorFactory( |
| 141 | + MainComponent.Builder builder); |
| 142 | +} |
| 143 | +``` |
| 144 | + |
| 145 | + |
| 146 | +続いてアプリケーションクラス用のAppModule. |
| 147 | + |
| 148 | +```java |
| 149 | +package com.yuki312.androiddaggersample; |
| 150 | + |
| 151 | +import dagger.Module; |
| 152 | + |
| 153 | +@Module(subcomponents = { MainComponent.class }) |
| 154 | +public class AppModule { |
| 155 | +} |
| 156 | +``` |
| 157 | + |
| 158 | +続いてAppComponent |
| 159 | + |
| 160 | +```java |
| 161 | +package com.yuki312.androiddaggersample; |
| 162 | + |
| 163 | +import dagger.Component; |
| 164 | +import dagger.android.AndroidInjector; |
| 165 | +import dagger.android.support.AndroidSupportInjectionModule; |
| 166 | + |
| 167 | +@Component(modules = { AndroidSupportInjectionModule.class, AppModule.class, MainModule.class }) |
| 168 | +public interface AppComponent extends AndroidInjector<App> { |
| 169 | + |
| 170 | + @Component.Builder |
| 171 | + abstract class Builder extends AndroidInjector.Builder<App> { |
| 172 | + } |
| 173 | +} |
| 174 | +``` |
| 175 | + |
| 176 | +`modules={...}`にはインジェクションモジュールを含める必要があります. |
| 177 | +インジェクションモジュールには次の種類が用意されています. |
| 178 | + |
| 179 | + - `AndroidInjectionModule.class`(サポートライブラリを使わない場合) |
| 180 | + - `AndroidSupportInjectionModule.class`(サポートライブラリを使う場合) |
| 181 | + |
| 182 | +インジェクションモジュールには, AndroidのコアコンポーネントにinjectするComponent/SubComponentのファクトリクラスである`AndroidInjector.Factory`を値に持つMapがAndroidコアコンポーネント毎に定義されており, それぞれのインスタンスはマルチバイインディングの仕組みで構築されています. |
| 183 | + |
| 184 | +``` |
| 185 | +@Module |
| 186 | +public abstract class AndroidInjectionModule { |
| 187 | + @Multibinds |
| 188 | + abstract Map<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>> |
| 189 | + activityInjectorFactories(); |
| 190 | +
|
| 191 | + @Multibinds |
| 192 | + abstract Map<Class<? extends Fragment>, AndroidInjector.Factory<? extends Fragment>> |
| 193 | + fragmentInjectorFactories(); |
| 194 | +
|
| 195 | + @Multibinds |
| 196 | + abstract Map<Class<? extends Service>, AndroidInjector.Factory<? extends Service>> |
| 197 | + serviceInjectorFactories(); |
| 198 | + ... |
| 199 | +``` |
| 200 | + |
| 201 | +`AndroidInjectionModule`, `AndroidSupportInjectionModule`が`AndroidInjector.Factory`の管理に必要であることがわかります. |
| 202 | +アプリケーション全体に渡るコアコンポーネントを管理するため, 基本的にはApplicationスコープのコンポーネントで管理することになります. |
| 203 | +AppComponentにはビルダー`AndroidInjector.Builder`も忘れずに定義しておきます. |
| 204 | + |
| 205 | + |
| 206 | + |
| 207 | +### DaggerApplication |
| 208 | + |
| 209 | +次にApplicationクラスの定義です. |
| 210 | +Applicationクラスには各Androidコアコンポーネント用の`AndroidInjector`を定義する必要があります. |
| 211 | +`AndroidInjector`はActivityやFragmentといったコアコンポーネントに依存性を注入するためのインジェクター用のインタフェースです. |
| 212 | +コアコンポーネント用のインジェクターには次のものがあります. |
| 213 | + |
| 214 | + - HasActivityInjector |
| 215 | + - HasFragmentInjector, |
| 216 | + - HasServiceInjector, |
| 217 | + - HasBroadcastReceiverInjector, |
| 218 | + - HasContentProviderInjector |
| 219 | + - HasSupportFragmentInjector(dagger-android-support) |
| 220 | + |
| 221 | +それぞれのインタフェースには各コアコンポーネント専用のインジェクターを返すメソッドが定義されているわけですが, Applicationクラスでこれら全てのインジェクターを実装するのは面倒なので, Dagger2.11では`DaggerApplication`クラスが提供されました. |
| 222 | + |
| 223 | + - dagger.android.DaggerApplication(サポートライブラリを使わない場合) |
| 224 | + - dagger.android.support.DaggerApplication(サポートライブラリを使う場合) |
| 225 | + |
| 226 | +Dagger2.11-rc1ではサポートライブラリ対応/非対応でクラス名が同じなのでextendsする際には注意が必要です. |
| 227 | +また, DaggerApplicationはApplication用のインジェクターを返す`applicationInjector`をabstractメソッドとして定義してあるので, これをオーバーライドしておきます. |
| 228 | +これで, Applicationクラスへのフィールドインジェクションもサポートされます. |
| 229 | + |
| 230 | +```java |
| 231 | +package com.yuki312.androiddaggersample; |
| 232 | + |
| 233 | +import dagger.android.AndroidInjector; |
| 234 | +import dagger.android.support.DaggerApplication; |
| 235 | + |
| 236 | +public class App extends DaggerApplication { |
| 237 | + |
| 238 | + @Override protected AndroidInjector<? extends DaggerApplication> applicationInjector() { |
| 239 | + return DaggerAppComponent.builder().create(this); |
| 240 | + } |
| 241 | +} |
| 242 | +``` |
| 243 | + |
| 244 | + |
| 245 | +### 仕上げ |
| 246 | + |
| 247 | +最後の仕上げにMainActivityでフィールドインジェクションを実装しましょう. |
| 248 | + |
| 249 | +```java |
| 250 | +package com.yuki312.androiddaggersample; |
| 251 | + |
| 252 | +... |
| 253 | +import dagger.android.AndroidInjection; |
| 254 | + |
| 255 | +public class MainActivity extends AppCompatActivity { |
| 256 | + |
| 257 | + ... |
| 258 | + |
| 259 | + @Override protected void onCreate(Bundle savedInstanceState) { |
| 260 | + AndroidInjection.inject(this); |
| 261 | + super.onCreate(savedInstanceState); |
| 262 | + ... |
| 263 | + } |
| 264 | +} |
| 265 | +``` |
| 266 | + |
| 267 | +`AndroidInjection.inject(this);`. たったこれだけです! 簡単ですね:) |
| 268 | +従来のComponentやModuleの指定が現れないのでDependency Injectionの原則にも忠実です. |
| 269 | + |
| 270 | + |
| 271 | + |
| 272 | +### おまけ |
| 273 | + |
| 274 | +#### dagger-android-support は何者か |
| 275 | + |
| 276 | +`dagger.android`の肝はAndroidコアコンポーネントへのインジェクションサポートです. |
| 277 | +今回登場した `HasSupportFragmentInjector`, `AndroidSupportInjectionModule`, `dagger.android.support.DaggerApplication`が主にサポートライブラリ向けのクラスになります. |
| 278 | +これらの中身を覗くと, `android.support.v4.app.Fragment`のためのバインディングマップであったり, インジェクターであったりの処理が定義されています. |
| 279 | +つまり, サポートライブラリのFragmentを使ったinjectionをサポートするためにこれらのライブラリが必要になってきます. |
| 280 | +サポートライブラリのFragmentを使わないのであれば必ずしも必要というわけではなさそうですね. |
| 281 | + |
| 282 | + |
| 283 | +#### コアコンポーネントのInjectorはどうやって選ばれる? |
| 284 | + |
| 285 | +ActivityやFragmentといったコアコンポーネントのインジェクターは`AndroidInjectionModule`に定義された`AndroidInjector.Factory`から生成することができますが, これが設定されているマルチバインディングで構築されたMapからファクトリインスタンスを取り出す操作は`DispatchingAndroidInjector`が行なっています. |
| 286 | +`DispatchingAndroidInjector`はDaggerが生成するオブジェクトであるためアプリケーション側から直接触ることはないと思いますが, `dagger.android`の内部動作を把握するには押さえておく必要のあるクラスです. |
| 287 | + |
| 288 | + |
| 289 | +#### ContentProviderInjectorとApplicationInjector |
| 290 | + |
| 291 | +Androidの仕組み上, アプリケーションプロセスがCygoteからforkされて開始される際, ContentProviderの初期化はApplicationの初期化より早いです. |
| 292 | +つまり, ActivityやBroadcastReceiver, Serviceなど他のコアコンポーネントと唯一異なってContentProviderのonCreate時にはまだApplicationクラスが初期化(onCreate)されていない可能性があります. |
| 293 | +DaggerApplicationクラスを覗くとこの辺りをどう解決しているのかをうかがい知ることができます. |
| 294 | + |
| 295 | +```java |
| 296 | + // injectIfNecessary is called here but not on the other *Injector() methods because it is the |
| 297 | + // only one that should be called (in AndroidInjection.inject(ContentProvider)) before |
| 298 | + // Application.onCreate() |
| 299 | + @Override |
| 300 | + public AndroidInjector<ContentProvider> contentProviderInjector() { |
| 301 | + ... |
| 302 | + |
| 303 | + |
| 304 | + /** |
| 305 | + * Lazily injects the {@link DaggerApplication}'s members. Injection cannot be performed in {@link |
| 306 | + * Application#onCreate()} since {@link android.content.ContentProvider}s' {@link |
| 307 | + * android.content.ContentProvider#onCreate() onCreate()} method will be called first and might |
| 308 | + * need injected members on the application. Injection is not performed in the the constructor, as |
| 309 | + * that may result in members-injection methods being called before the constructor has completed, |
| 310 | + * allowing for a partially-constructed instance to escape. |
| 311 | + */ |
| 312 | + private void injectIfNecessary() { |
| 313 | + if (needToInject) { |
| 314 | +``` |
| 315 | + |
| 316 | + |
| 317 | +この他にも, コアコンポーネントのComponent/Module定義を簡略化できる`@ContributesAndroidInjector`や, コアコンポーネントインスタンスをパラメータにとるProviderメソッドの提供方法などもありますが, 本稿では割愛します. |
| 318 | + |
| 319 | +ひとまず, `dagger.android`パッケージがどのようなものになる予定なのか, 本稿で大まかにでも掴めたようでしたら幸いです. |
0 commit comments