Dagger 2: Component dependencies

# 概要

本記事では Dagger 2: Component dependencies の使用例を具体的に解説してみます。

Dagger 2 では、中央から末端に向かって依存関係を宣言していくのが基本になりますが…

そうならないケースが稀にあります。それは androidTest / test から main を参照する場合です。

例を作りました
https://github.com/kenjiuno/Dagger2ComponentDependenciesTest

例は EmptyActivity で作ったプロジェクト Dagger2ComponentDependenciesTest の MainActivity に RandomNumberGenerator の依存関係を充足させるだけの単純なものです。

本題は… `public @interface Component` に、ちょこっとだけ解説のある [Component dependencies](https://dagger.dev/api/2.27/dagger/Component.html#component-dependencies) の使い方を説明することで、 ExampleInstrumentedTest を改造しました。

## 引用

> Component dependencies
While subcomponents are the simplest way to compose subgraphs of bindings, subcomponents are tightly coupled with the parents; they may use any binding defined by their ancestor component and subcomponents. As an alternative, components can use bindings only from another _component interface_ by declaring a [component dependency](https://dagger.dev/api/2.27/dagger/Component.html#dependencies--). When a type is used as a component dependency, each [provision method](https://dagger.dev/api/2.27/dagger/Component.html#provision-methods) on the dependency is bound as a provider. Note that only the bindings exposed as provision methods are available through component dependencies.

# main/*

Dagger 2 を使った構成について。

Component は、アプリケーションから見えるインターフェイスです。 

[Members injection](https://dagger.dev/members-injection.html) に必要な `inject` メソッドや、ファクトリーメソッドを備えましょう。

Module は、インスタンスの作成方法を開発者が記述します。アプリケーションから Module は見えません。隠蔽されます。

ビルドの際に Dagger 2 が Module / Component の依存関係を解決して、Component の実装クラスを提供します。

```plantuml

package main {
    interface AppComponent {
        + void inject(MainActivity activity)
        + RandomNumberGenerator newRandomNumberGenerator()
    }

    class MainActivity {
        + @Inject RandomNumberGenerator randomNumberGenerator
    }

    class AppModule {
        + @Provides RandomNumberGenerator newRandomNumberGenerator()
    }

    AppComponent -- AppModule
    AppComponent -- MainActivity
}
```

## MainActivity

RandomNumberGenerator を inject してもらっています。

```MainActivity.java
public class MainActivity extends AppCompatActivity {
    @Inject
    RandomNumberGenerator randomNumberGenerator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DaggerAppComponent.builder()
                .build()
                .inject(this);

        ((TextView) findViewById(R.id.my_message)).setText(
                "Your lucky number is a: " + randomNumberGenerator.getNext()
        );
    }
}
```

## AppComponent

MainActivity の依存を充足するためのメソッド inject を備えます。

```AppComponent.java
@Component(modules = {AppModule.class})
public interface AppComponent {
    void inject(MainActivity activity);

    RandomNumberGenerator newRandomNumberGenerator(); // ※
}
```

`※` の行が Component dependencies で肝要となる [Provision methods](https://dagger.dev/api/2.27/dagger/Component.html#provision-methods) です。これがないとつぎのようなエラーになります…

```
H:\Proj\Dagger2ComponentDependenciesTest\app\src\androidTest\java\com\example\dagger2componentdependenciestest\di\TestAppComponent.java:8: エラー: [Dagger/MissingBinding] com.example.dagger2componentdependenciestest.utility.RandomNumberGenerator cannot be provided without an @Inject constructor or an @Provides-annotated method.
public interface TestAppComponent {
       ^
      com.example.dagger2componentdependenciestest.utility.RandomNumberGenerator is injected at
          com.example.dagger2componentdependenciestest.ExampleInstrumentedTest.randomNumberGenerator
      com.example.dagger2componentdependenciestest.ExampleInstrumentedTest is injected at
```

## AppModule

RandomNumberGenerator の作り方が書いてあります。

```AppModule.java
@Module
public class AppModule {
    @Provides
    RandomNumberGenerator newRandomNumberGenerator() {
        return new RandomNumberGenerator();
    }
}
```

## RandomNumberGenerator

必要ないとは思いますが一応…

```RandomNumberGenerator.java
public class RandomNumberGenerator {
    private int seed = 0;

    public int getNext() {
        seed += 7;
        return seed;
    }
}
```

## androidTest/*

ここからは [インストゥルメント化単体テスト](https://developer.android.com/training/testing/unit-testing/instrumented-unit-tests?hl=ja) でのみ使用する、androidTest の中の部分です。

通常 Component の依存関係を満足するには Module が必要になります。しかし Component dependencies を利用しますと Module の指定が無くとも依存先の Component を使用して、依存関係を満足できるようになります。

この場合は TestAppComponent が AppComponent に依存しています。

```plantuml
@startuml main
!theme mars

package main {
    interface AppComponent {
        + void inject(MainActivity activity)
        + RandomNumberGenerator newRandomNumberGenerator()
    }

    class MainActivity {
        + @Inject RandomNumberGenerator randomNumberGenerator
    }

    class AppModule {
        + @Provides RandomNumberGenerator newRandomNumberGenerator()
    }

    AppComponent -- AppModule
    AppComponent -- MainActivity
}

package androidTest {
    interface TestAppComponent {
        + void inject(ExampleInstrumentedTest test)
    }

    AppComponent --- TestAppComponent

    class ExampleInstrumentedTest {
        + @Inject RandomNumberGenerator randomNumberGenerator
    }

    TestAppComponent -- ExampleInstrumentedTest
}

@enduml
```

ExampleInstrumentedTest で必要とされている RandomNumberGenerator インスタンスはどのように解決されるのでしょうか。

ここで注意が必要なのは TestAppComponent からは「AppModule は不可視である」という点です。

という事は AppComponent で RandomNumberGenerator の提供方法を示す必要があります。

AppModule では RandomNumberGenerator の作成方法を示し、
AppComponent では RandomNumberGenerator の提供方法を示します。

RandomNumberGenerator の作成時に追加の情報が不要であれば、パラメータ無しのファクトリーメソッドを定義します。

```java
@Component(modules = {AppModule.class})
public interface AppComponent {
    ...

    RandomNumberGenerator newRandomNumberGenerator();
}
```

## TestAppComponent

AppComponent を参照しています。

ExampleInstrumentedTest の充足手段を宣言しています。

```TestAppComponent.java
@Component(dependencies = {AppComponent.class})
public interface TestAppComponent {
    void inject(ExampleInstrumentedTest test);
}
```

## ExampleInstrumentedTest

MainActivity と同様に RandomNumberGenerator を注入して頂いています。

```ExampleInstrumentedTest.java
/**
 * Instrumented test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Inject
    public RandomNumberGenerator randomNumberGenerator;

    @Test
    public void useAppContext() {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();

        assertEquals("com.example.dagger2componentdependenciestest", appContext.getPackageName());

        DaggerTestAppComponent.builder()
                .appComponent(
                        DaggerAppComponent
                                .builder()
                                .appModule(new AppModule())
                                .build()
                )
                .build()
                .inject(this);

        assertNotEquals(randomNumberGenerator.getNext(), 1234567890);
    }
}
```

# 結論

以上で `androidTest / test から main を参照` する場合に有効な手段となるであろう `Dagger 2: Component dependencies の使用例` を解説することができました…