Dagger2を味見してみたやDagger2を使って依存性の存在する箇所をテストしてみたでは、簡単な自分で作ったModuleに対して、テスト時だけ返り値を置き換える、という簡単な例を使ってDIを試してみました。
今回は、SharedPreferenceのような外部ライブラリに依存する箇所をうまく置き換え、Unit test / Integration testレベルにおけるtestabilityを向上させることを目的に追加で味わってみました。ここまでくると、大分頭の理解としては馴染んだ感があります。
元になった記事はこの Snorkeling with Dagger 2 というものです。
http://konmik.github.io/snorkeling-with-dagger-2.html
以下では、この記事を読んで、頭になじませながら備忘録として書いたもの。
@Inject や @Module をつけたメソッドから、コンパイルによって @Component つきのブリッジが生成されて @Inject のフィールドへひも付けられる流れなんかも書かれています。Dagger prefixがついているメソッドの生成、MembersInjector を持ったインスタンスの生成という流れも載っています。
@Inject と @Module、 @Providorのひも付き方は、以下の図つきで説明されていました。

@Inject アノテーションは、以下で同じ意味を持ちます。個人で試行錯誤しながら学んでいるときも、public付きでないといけないと警告もらったりしていて、なるほどという感じ。
public class PreferencesLogger {
@Inject SharedPreferences pref;
@Inject
public PreferencesLogger() {
}
...
Dagger prefixのつくメソッドは、コンパイルされなければ生成されないコードが関係します。そのため、コンパイルするまではエラーが表示され続けます。
そこに対しても言及されていて、著者がDagger2Helperを作ってそれを使ったエラー回避の方法や、reflectionを使った回避方法を書いていたりします。なるほど。
Tipsとして、injectionの方法を共有していました。
通常は MyApplication.inject(...) と書くのですが、ここは MainInjector.getComponent().inject(...) としてもかけるそうな。
他、reflectionを使わずにinjectionする方法も書いています。Dagger2Helperを使ってApplicationクラスにinjectを定義しておき、そのApplicationクラスを他クラスから@Injectで直接とってきて使うという方法。
この方法の性能面での有用性も書いていて、なるほどという感じでした。
A direct injection on a subclass takes about 0.0013 ms, a reflected injection on a superclass takes about 0.014 ms. Both prices are negligible.
以下、記事とは関係なく私の所感。
@Providorされた @Module を、 injectしたいクラスもしくは親クラスにまずは以下のようにブリッジを作ってひも付けてあげることが大事ですね、というのがDagger2でキモ。
SampleComponent component = Dagger_SampleComponent
.builder()
.sampleModule(new SampleModule(activity))
.build();
component.inject(activity);
あとは、SampleModuleを継承したモジュールを定義して、テスト時にはその継承したモジュールで必要な箇所をOverrideしてから依存関係を隠蔽してtestabilityをあげる、と。
SharedPreferenceをinjectionするとき、どういう粒度でModuleに入れ込むのが良いのかな、と少し考えています。多分、injectionを行うときの粒度をどのくらいにするかというところは少し考えるところなのかなと思います。(なれたらそうでもないのかも?)SharedPreference自体をinjectionするようにするか、その一部機能(例えば、依存性のある読み込み箇所のみ、など)にしたほうが良いのか。責務を考えるとSharedPreference全体のように役割ごとにまとめる感じなのだろうなー。
SharedPreferenceの話をすると、 @Provides にはcontextを引数として与えることができないので、以下のような記述が必要になります。
@Module
public class SharedPreferenceModule {
private MyApplication myApplication;
public SharedPreferenceModule(MyApplication myApplication) {
this.myApplication = myApplication;
}
@Provides @Singleton
SharedPreferences provideSharedPreferences() {
return myApplication.getSharedPreferences(....);
}
}
これに対して、以下のような形で SharedPreferenceModule に対してcontextを渡します。
SharedPreferenceComponent component = Dagger_SharedPreferenceComponent
.builder()
.sharedPreferenceModule(new SharedPreferenceModule(myApplication))
.build();
component.inject(activity);