[Android]Dive into Toothpick

過去の調査に加えて、Toothpickに潜ってみました。

簡単なまとめ

  • @inject アノテーションが付いている要素を、 scope.getInstance(); したクラスでinjectionして利用することができる。
  • inject可能な要素は木構造で表現され、親scopeに含まれる要素をinjectしたメソッド/クラスとして利用することができる
    • scope外の要素を使おうとするとクラッシュ
  • bind(IFoo.class).to(Foo.class)Foo.class を使い回すことができるし、 bind(IFoo.class).to(new Foo()) では都度新しいinstanceを生成して利用可能
  • ActivityやFragmentといったライフサイクルに対して Toothpick.openScopes(getApplication(), this) してscopeを設定した場合、そのライフサイクルの onDestroy のタイミングでそのscopeをcloseする必要がある
  • Scopeはアノテーションベースでも独自で設定できる

導入

ライブラリとして利用するための導入は こちらのWiki に任せて省略。

例えば、以下のFooクラスに対してinjectしようとします。

class Foo {
  @Inject Foo() {...}

  @Inject Bar bar;
  @Inject void setQurtz(Qurtz qurtz) {...}
}

この時、scope.getInstance(Foo.class) したら、そのクラス内でこの Foo.class でinjectされている FooBarsetQurtz@Inject で指定して使い回すことが可能となります。

//creating an instance in a scope
Scope scope;
scope.getInstance(Foo.class);

ついでに bind に関して少し触れておきます。以下のようにbindすると、

bind(IFoo.class).to(Foo.class); // case 1
bind(IFoo.class).to(new Foo()); // case 2

それぞれのケースにおいて以下のようになります。

  • case1 では、すでにFoo.class のインスタンスが存在するならそれを使い、それをIFoo.classに紐付ける
  • case2 では、常に新たな Foo.class のインスタンスを使い、それをIFoo.classに紐付ける

Scopeの種類

Scopeの種類としては2つ存在します。

  • a binding
    • 都度、bindされるたびに新しいインスタンスとして利用される
    • 子要素では親要素を上書き可能
      • bind(IFoo.class).to(Foo.class); みたいな感じで都度bindされるやつです
  • scoped instances
    • injectionされたscope内で使い回される、Singletonのようなインスタンス
    • 子要素でも
      • Toothpick.openScope(getApplication(), this); のようにscopeとして設定されるやつです

ライフサイクルとの兼ね合い

Toothpickでは、Scopeという概念でinjectionする対象に木構造をもたせています。この構造を構築するために、 openScopes というメソッドが存在します。
Wikiはこちら

このopenScopesは、左=>右の順に親=>子という形でinjectする対象の関係を作ることができます。そのため、以下のような例の通り

Toothpick.openScopes(application, activity, fragment);
Toothpick.openScopes(application, service1);
ToothPick.openScopes(application, service2);

のようにすると、以下のような木構造が生成されます。

//Example of scopes during the life of an Android application
     Application Scope
       /         \    \
      /           \    \
Activity Scope     \   Service 2 Scope   
     /              \
    /          Service1 Scope
   /
Fragment Scope

これにより、 Activity Scope でinject出来るように指定されたものは、 Fragment Scope にだけ使うことができるようになります。Service1や2では使えません。使おうとした場合は クラッシュ します。
この構造を持つことで、injectionする対象の影響範囲を限定することができ、どこに存在するかわからないものをinjectするというような不安なことをする必要はなくなります。

この openScope の対になるものが closeScope です。このscopeはライフサイクルに応じてちゃんと閉じてあげる必要があります。閉じることで、そのクラスにinjectされる時に生成されたようなインスタンスはGCの対象になります。

例えば、Activityの onCreate で以下の通りした場合、onDestroy でそのActivityにinjectされたscopeをcloseします。

onCreate() {
  Toothpick.openScope(getApplication(), this); // Applicationの子としてこのクラスを設定。Applicaitonに設定されたinjectを利用可能になる。
}

...

onDestroy() {
  Toothpick.closeScope(this);
  suprt.onDestroy();
}

少し手間かもしれませんが、AndroidでonCreate/onDestroyのライフサイクルを意識することは必要なので、このくらいであればさほど手間にはならないのでないかな、という感じです。

Custom scope

@javax.inject.Scope によって、独自にscopeを定義することができます。これにより、annotationベースでinjectionしたい対象をbindすることが可能になります。Wikiはここら辺

annotationを使うと以下のようなscopeが飛んだ関係性を作ることができる( @Singleton@S2Singleton )のですが、これはtree構造の利点を享受できなくなるので、バットプラクティスとしているみたいです。柔軟な形を作ることはできるけれど、それは良くない反作用も得てしまうということですね。

Scope S0 = @Singleton //bound to the scope annotation @Singleton
  \
   \
  Scope S1 // not bound to any scope annotation
    \  
     \
    @S2Singleton //a scope bound to the scope annotation @S2Singleton

Nevertheless, this is the way we can migrate apps that were using the infamous ContextSingleton of RoboGuice. But remember, this is a bad practice, as it doesn’t take advantage of scope resolution, or blurs it and make it leak-prone.

custom scopeの作り方なんかは実際のWikiなどを見ながらの方が理解しやすいと思うので、ここでは省略。この記事に出る @Inject 以外のアノテーション(@ActivitySingletonとか)はこの機能を使ったもの、と見てもらって大丈夫です。

bindingされた要素の生成

このWikiのところでは、bindingされたクラスがどのようなルールで扱われるかが書いていました。ここはざっと把握していた方が良さそう。

class SimpleModule extends Module {
  SimpleModule() {
    bind(IFoo.class).to(Foo.class); // case 1
    bind(IFoo.class).to(new Foo()); // case 2
    bind(IFoo.class).toProvider(FooProvider.class); // case 3
    bind(IFoo.class).toProvider(new FooProvider()); // case 4
    bind(Foo.class); // case 5
  }
}

この時、case2と4は都度新しいインスタンスが生成され、その結果がIFoo.classにバインドされます。その他はすでに存在するインスタンスを使いまわします。そのため、case2とcase4ではインスタンスの破棄など、ちゃんと開発者が気をつける必要があります。

なので、以下のようなルールを敷くと良い、と書かれています。

As soon as Toothpick creates an object, its dependencies will be injected.

つまるとこ、

As soon as a developer creates an object with Toothpick, she has to take care of injecting this object dependencies.

とのこと。なるべくToothpickに任せましょう。

scope resolution

Wikiの子のページ
Toothpickでは木構造を常に上に遡ります。そのため、injectされた依存性も親方向だけを気にすれば良いです。

The core idea is that scopes are always bubbled up when resolving an injection, and looking up for a binding. Toothpick never goes down the scope tree.

Androidでは、なんだかんだでApplication classとかそん在するし、contextを意識したりすると縦横無尽に依存性を持たれると把握が困難になるのでこの制約はありなのかなと思います。

ここの実際はWikiを見る方が良いのですが、以下に例を載せます。

class DisplayImpl1 {@Inject Scope scope}
class DisplayImpl2 {@Inject Scope scope}
@Singleton class FooSingleton {@Inject IDisplay display; @Inject Scope scope}
@ActivitySingleton class FooActivitySingleton {@Inject Scope s; @Inject IDisplay display;}
@Singleton class FooSingletonError {@Inject FooActivitySingleton foo;}

この時、このクラスは以下のような関係を持ちます。

//Example of scopes during the life of an Android application

+----------------------------------------------------------------------------------+  Resolution
| +---------------------------------------------------------------+                |  space
| |  application scope = @Singleton :                             | Resolution     |  for Activity
| |        /                    - Scope --> (application)         | space          |  scope
| |       /                     - IDisplay --> DisplayImpl1       | for @Singleton |
| |      /                      - FooSingleton --> (FooSingleton) | scope          |  
| +-----/---------------------------------------------------------+                |
|   activity scope = @ActivitySingleton :                                          |
|                               - Scope --> (activity)                             |
|                               - IDisplay --> DisplayImpl2                        |
|                               - FooActivitySingleton --> (FooActivitySingleton)  |
+----------------------------------------------------------------------------------+

ここで、application/activityのscopeを見てみると以下な感じで観察できます。クラッシュするところだけ言葉を加えていますが、他はWikiを見てください。(省略している)

  • application scope を対象
    • scope.getInstance(Scope.class)
    • scope.getInstance(FooSingleton.class)
    • scope.getInstance(FooActivitySingleton.class)
    • scope.getInstance(FooSingletonError.class)
  • activity scope を対象
    • scope.getInstance(Scope.class)
    • scope.getInstance(IDisplay.class)
    • scope.getInstance(FooSingleton.class)
    • scope.getInstance(FooActivitySingleton.class)
    • scope.getInstance(FooSingletonError.class) : これは唯一クラッシュする。 @Singleton でinjectされているので、activityのscopeに入っていないので。

このscopeの利点としてメモリリークなどの幾つかの事例をWikiのここでは載せています。が、ここでは省略。

Scoped & Unscoped Bindings

Bindingに関する話。wikiではここ

bindingには2種類存在します。

  • unscoped bindings
  • scoped bindings

これを、以下を例にして考えます。

class A {
  @Inject IFoo foo1;
  @Inject IFoo foo2;
}

class Foo {
  @Inject Scope s;
}

これの違いは、bindされたスコープは、その影響がScope S2まで及ばない、ということです。

unscoped binding

  • bind(IFoo.class).to(Foo.class)
Scope s0 : Scope --> S0
  \
   \
  Scope S1 : Scope --> S1 & IFoo --> Foo
    \
     \
    Scope S2 : Scope --> S2

scoped binding

  • bind(IFoo.class).to(Foo.class).scope()
Scope s0 : Scope --> S0
  \
   \
  Scope S1 : Scope --> S1 & IFoo --> (Foo)  // <-- scoped binding
    \
     \
    Scope S2 : Scope --> S2

これの実際のscopeは以下のようになります。これを見ても、確かにS2に影響を及ぼさない形になっていますね。(実際にテストコードなどで動かしてみると良さそう)

//space of creation of Foo instances in the case of a scoped binding in S1.
+--------------------------------------------------------------------+
|    Scope s0 : Scope --> S0                                         |
|         \                                                          |
|          \                                                         |
|   Scope S1 : Scope --> S1 & IFoo --> (Foo)  // <-- scoped binding  |
|          \                                                         |
+-----------\--------------------------------------------------------+
             \
        Scope S2 : Scope --> S2

scopedできる/できないは、以下のようにinjectする都度 new で新たなインスタンスを生成するかどうか、のようです。

bind(IFoo.class).to(Foo.class); // can be scoped
bind(IFoo.class).to(new Foo()); // cannot be scoped (it is indeed scoped)
bind(IFoo.class).toProvider(FooProvider.class); // can be scoped, will scope both the provider and produced instances
bind(IFoo.class).toProvider(new FooProvider()); // cannot be scoped (the provider is scoped)
bind(Foo.class); // can be scoped

factoryの生成

Wikiはこれ

Toothpick will always inject all the dependencies (expressed by injected constructors, injected fields or injected methods) of all instances it creates.

Toothpickは、 @Inject を持っているClassに対して、Factoryを自動生成します。そのFactoryを最適化するよな挙動があるのですが、それが適用される/されない場合を書いています。最適化の説明はこちらを参考にということで、ここではパターンだけ並べておきます。

(私の備忘録というか、メモ込みで。)

Classes with @Inject annotated members (fields or methods)

public class Foo {
  @Inject Foo() {...}
}
  • for the class Foo (optimistic)
  • for the class Bar (optimistic)

Constructors with parameters

class Foo {
 @Inject Foo(Bar bar);
}

for the class Foo (non optimistic factory, this is the normal case)
for the class Bar (optimistic)

Scope annotated class

@ActivitySingleton
class Foo {
}
  • for the class Foo (optimistic)

Factories and scopes

scoped annotationに対するFactoryはいかのように

//a scoped Factory
public final class Foo$$Factory implements Factory<Foo> {
  @Override
  public Foo createInstance(Scope scope) {
    scope = scope.getParentScope(ActivitySingleton.class);
    Bar bar = scope.getInstance(Bar.class); 
    return new Foo(bar);
  }
}

Member Injection

public class Foo {
  @Inject void m(Bar bar) {...}
}
//a Member Injector
public final class Foo$$MemberInjector implements  MemberInjector<Foo> {
  @Override
  public void inject(Foo foo, Scope s) {
    Bar bar = Toothpick.getInstance(Bar.class);
    return foo.m(bar);
  }
}

Factories & Member Injectors

public class Foo {
  @Inject Bar bar; //an injected field
  @Inject Foo() {...} //an injected constructor
}
//a simplified Factory
public final class Foo$$Factory implements Factory<Foo> {
  @Override
  public Foo createInstance(Scope scope) {
    Foo foo = new Foo();
    new Foo$$MemberInjector().inject(foo, scope);
    return foo;
  }
}

Member Injectors and inheritance

class Foo {
  @Inject Bar bar;
}

class FooChild extends Foo {
  @Inject Qurtz qurtz;
}
  • この場合は、 FooChildFoo のmember injectorを使うようです。つまり、 FooChildFooChild のmember injectorと、Foo のmember injectorを持つことになる模様。

締め

ざっと、ToothpickのWikiとサンプルコードを動かしながら、Tree Based DIのコンセプトと使いかたを学んでみました。

AndroidのDIではDagger2が最近では多いと思います。一方で、個人的にはDagger2を学んだ時には頭に馴染むまでに少し時間がかかりました。

このToothpickはAndroidのContextに沿った形でも考えられていて、個人的にDagger2を学ぶよりは学習は低かったです。個人的には、Androidアプリという文脈だとこのTree Based DIもありなのではないかなと感じます。
テスト機構も提供されているのが個人的にはGoodでした。

Dagger2との速度比較では、injectするメソッド数が1000超えないくらいだと大差ないので、使いやすさ次第になりそうですね。どうだろう。完全なAndroiderの人の反応が気になるところ。

Advertisements

[Android]ToothpickでTree Based DI

最近、Tree Based DIツールのToothpickを知りました。

https://github.com/stephanenicolas/toothpick

このメンテナは、RoboGuiceをメンテしていたGrouponの人です。RoboGuiceの更新を見に行った時にたまたま見つけました。ここ数ヶ月で作り上げられたDIライブラリのようです。このリポジトリWikiのあるページに、AndroidにおけるDIの歴史や経験をまるっと学ぶことができて良かったです。

https://github.com/stephanenicolas/toothpick/wiki/FAQ#why-creating-toothpick-

さらには、彼らがRoboguiceからDagger1/2と触れた上でこのToothpickを作るに至った流れも書いています。

まだWikiと簡易サンプルをさっと眺めた程度なのですが、備忘録がてら。

AndroidでToothpick

Toothpick自体はJavaのTree Based DIです。これ単体ではAndroidはサポートしていません。Androidをサポートするために、smootheがあります。

このサンプルにあるように、Toothpickはsmoothieを介してAndroidにDIを提供します。

幾つかのDIツールとの速度比較

Roboguice4/Dagger1/Dagger2/Toothpickのinjectionするメソッドの個数による速度差が以下の通り。

https://github.com/stephanenicolas/toothpick/wiki/Benchmark#benchmark-raw-data

Androidの環境でいうと、injectionするメソッド数が1000個超えるとかな大規模なものになることはまだそこまで多くはないはずなので、速度としては申し分ないのではないでしょうか。

初期設定では一部reflectionを使っている

以下の通り、default設定ではfactoriesの読み込みやmember injectorsを読み込むにあたりreflectionを使っているらしいです。

https://github.com/stephanenicolas/toothpick/wiki/Configurations

ただ、このreflectionはOFFにすることができるそうな。そのためには以下の通りToothpickを使う時に reflectionFree の設定を与える必要があるとのこと。これにより、完全にToothpickはreflectionなく動作し、高速になると。

Toothpick.setConfiguration(Configuration.reflectionFree());
FactoryRegistryLocator.setRootRegistry(new <package>.FactoryRegistry());
MemberInjectorRegistryLocator.setRootRegistry(new <package>.FactoryRegistry());

ベンチマークはこちら =>

確かに、reflectionの有無で何倍も速度差がでますね。ここら辺の積み重ねで性能差が大きく出てきそうな。

scopeにより意図しないクラッシュを避ける

Scopeという概念を入れることで、Scopeに属さないfragmentなんかを意図せず読んだ時にクラッシュする、というような不用意なinjectionを回避できるようにしているそうな。ここはまだこのツールの中身を追ってはいないのでなるほどなという感じ。

あと、ここなんかでは、メモリリークに対するこのツールの見解といったところも記述されています。

締め

まだざっとWikiなんかを眺めただけで中身を追ってはいないのですが、tree based DIが、学習コストがあまり高くなくとも比較的容易に使えるライブラリなのであれば結構有力なDIの選択肢になるのではないかなと感じています。個人的には、toothpickにはテストコード向けの機能も提供しているので、テストコード書くにあたってもかきやすいものであればなお嬉しいなという感じです。シンプルにinjectionするモジュールの関係性や使い方を理解して使えるのであれば、内部品質向上へ大きく寄与するし、テストコードを描きやすいのであればクラッシュや機能不全などの外部品質の担保にも寄与できると期待できるためです。

ちなみに、過去、私はDagger2を学んでいました。このBlogの過去のDagger2関係 => 検索
その中でDagger2を使ってテストコードのDIによるテストも書きはしたのですが、そんなに簡単に書けた!というほどでもなかった記憶が。。。

ともあれ、Dagger2と同じくらいには使い方などを追ってみようと思っています。(少し気長に…)

Snorkeling with Dagger 2でDagger2に潜った

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);

Dagger2を使って依存性の存在する箇所をテストしてみた

Dagger2を味見してみたDependency Injectionに関して今更だけど学んだを経て、Dagger2を触ってみました。

簡単な、Injectionの機構を使って、依存関係のある場所をテスト時だけ異なる依存関係にしてテストを実施する、というところまで達成できました。よかったよかった。そして、ある程度理解できたので、あとは実際の実装に落とし込みながらなじませる、が必要そう。

確認した内容はごくごく簡単で、表示する文言をビルド環境によって変更するというもの。Dagger2では、injectionするために@Moduleのアノテーション、@Providerのアノテーションつけられたものに対して以下のようにModuleをセットします。そこで、 SampleTestModule のように依存関係を拡張したModuleを渡してあげれば良いだけ。

Dagger2はコンパイル時に依存性が注入されるので、コンパイルされるまでは DaggerSampleApplication_SampleApplicationComponent に警告が表示されますが、それは気にしない。

通常

      SampleComponent component =
                DaggerSampleApplication_SampleApplicationComponent
                        .builder()
                        .sampleModule(new SampleModule())
                        .build();

依存性注入

      SampleComponent component =
                DaggerSampleApplication_SampleApplicationComponent
                        .builder()
                        .sampleModule(new SampleTestModule())
                        .build();

トラブルシューティング

以下の2点、気をつける必要があった。

compileOptionの追加

Gradleプロジェクトでは以下を追加しましょう。

    packagingOptions {
        exclude 'META-INF/services/javax.annotation.processing.Processor'
    }

annotationライブラリの追加

Gradleプロジェクトでは以下を追加しましょう。

    compile 'javax.annotation:jsr250-api:1.0'

ref: http://stackoverflow.com/questions/25090570/google-auto-factory-not-annotated-with-provided

その他

終わったあとに見てみたのだが、以下記事も参考になりそう。

ButterKifeとDagger2は触って感覚を覚えたので、今度はRoboGuiceかな。

testotips.ioで、依存性を持つところのテストTipsとか共有したい機運だ。

Dagger2を味見してみた

Dependency Injectionの、Dagger1とDagger2を学んで、DIの理解を深めてみる。Tasting Dagger 2 on Androidを参考にしました。

  • Dagger1はGuiceに影響を受けている。特徴は以下。Compile time injection。
    • Multiple injection points: dependencies, being injected.
    • Multiple bindings: dependencies, being provided.
    • Multiple modules: a collection of bindings that implement a feature.
    • Multiple object graphs: a collection of modules that implement a scope.
  • Dagger2はAutoValue projectの影響を受けているらしい。
    • No reflection at all: graph validation, configurations and preconditions at compile time.
    • Easy debugging and fully traceable: entirely concrete call stack for provision and creation.
    • More performance: according to google they gained 13% of processor performance.
    • Code obfuscation: it uses method dispatch, like hand written code.

Dagger2をTasting

  • @Inject
    • 依存性を要求する。Daggerはこのアノテーションされたクラスと、満足する依存性を構築する。
  • @Module
    • 依存性を提供するメソッドのクラスを示す。Daggerはクラスのインスタンスを生成するときに依存性が存在する場所を知ることができる。
    • モジュールが分割されたり、一緒に構成されるように設計されている
  • @Provide
    • モジュールの内部において、Daggerにどのように依存性を構成し、提供してほしいかを記述する。
  • @Component
    • @Injectと@Moduleの間のブリッジ
    • 定義したすべての型のインスタンスを提供する
      • @Componentを付けたい、依存している対象を構成する@Moduleすべてを関連付ける必要がある。それがないと、コンパイル時に正しく注入の関係を作れずにエラーになる。
      • 依存しているproviderを、moduleを通して知る。
  • @Scope
    • Dagger2にて、カスタムアノテーションを通じてより具体的なスコープを絞る方法を提供する
      • @PreActivityアノテーション
      • Activityが生存している間のみ生存しているようにする
  • @Qualifier
    • 依存性を区別するにクラスの型が不十分であるときに使われる
    • 例えば、Androidはcontextが異なるとactivityなど異なることが多い。@ForApplicationや@ForActivityでは、特定のcontextに依存するActivityなどのみを提供する

Dagger2における依存性注入方法

  1. Constructor injection: by annotating the constructor of our class with @Inject.
  2. Field injection: by annotating a (non private) field of our class with @Inject.
  3. Method injection: by annotating a method with @Inject.

ActivityやFragmentsに対してはfield injectionが良いらしい。なぜなら、それらのconstructorsにアクセスできないから。

具体例

Our example の欄をみる。その例題となるコードは以下。

なにとなく、

  • @Providesには providor prefixをつける
  • @Moduleには Module suffixをつける
  • Daggerを使ったコンポーネントのimplemtationを作るときは Dagger prefixをつける

などの慣習が書かれている。

例はコード読みながらでないと頭に入ってこないので、頭に馴染むまで何度か読もう。

依存

@Component では、その引数として modulesdependencies を付与したり、それらを複数与えることができる。少し使い方にモヤモヤしていたのですが、以下の Application ComponentActivity ComponentUser Component のどれに依存してinjectしたいModuleなのかと、その @Component にひも付けたいmodulesなのかをたどっていくと、理解が深まったきがする。

例えば、例として挙げられているUserComponentは以下のコードとして宣言されています。これはActivityComponentを継承していて、この例より前の箇所でActivityComponentはActivityModuleと関係づけられています。なので、UserComponentではActivityModuleもひも付ける必要がある。あとは、このActivityComponentはのアクティビティはApplicationと同じライフサイクルを持っている必要があると宣言されているので、こんUserComponentもActivityComponentと同様にApplicationComponentと依存関係を持つ必要がある。

@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = {ActivityModule.class, UserModule.class})
public interface UserComponent extends ActivityComponent {
  void inject(UserListFragment userListFragment);
  void inject(UserDetailsFragment userDetailsFragment);
}

他のサンプル

Dagger 1 to 2 migration process

この以下画像とその説明で、依存関係のひも付かれかたがさらに馴染んできました。自分がどのmoduleに依存するのかを宣言することで、依存関係を保持したいmodule間を指定できるのですね。なるほど。


サンプル読んで、もう少し慣れを進めよう。
あと、@Component付近の、コンパイル前の依存性を書くところは時折見返さないとすぐこんがらがりそうだだ。

Dependency Injectionに関して今更だけど学んだ

最近、Android開発でテストを構築していくとき、Dependency Injectionは避けて通れないものだなと感じたので、DIを頭になじませるために本腰を入れて学んでみることにしました。

DIの概念や簡単な説明自体は少しWebを探せば見つかるのですが、それが頭に馴染むのには少し時間がかかった…
あと、実装コードをもう少し手を動かさないとな、という感じです。

objc.ioの記事から

Dagger2とか、RoboGuiceとか、もっとテストコードをよくするためになじませる必要がありそう。