Dagger2のscopeの使い方を正しく理解する

## このpostについて

- Dagger2には `@Singleton` を始めとした "scope" というものがあります
- 下記に１つでも該当する人 (= この前までの自分) は読んでもらえると良いかもです

```
- Dagger2はActivity lifecycleを克服するために有用な手段だと思ってる
- @Singleton は、いわゆる getInstance() を用いたstaticなシングルトンを実現するためのアノテーションだと思ってる
- Daggerのscopeは、インスタンスのlifecycleをよしなにゴニョゴニョしてくれるmagicであると思ってる
```

## Scopeって何なんだろう

### 例を交えて考えてみます

- `MyViewModel` というクラスのinstanceをinjectionするケースを考えます
- "injectする度にinstanceを生成したくない & 一度作ったものを使いまわしたい" という際には下記のようにcomponentとproviderを宣言しますよね

```java
@Singleton
@Component(modules = { MyModule.class })
interface MyComponent {

    void inject(MainActivity activity);

    MySubComponent createMySubComponent();
}
```

```java
@Module
class MyModule {
    @Provides
    MyFakeApi provideMyFakeApi() {
        return new MyFakeApi();
    }

    @Singleton
    @Provides
    MyViewModel provideMyViewModel(MyFakeApi api) {
        return new MyViewModel(api);
    }
}
```

- こうすることで `provideMyViewModel` は一度しか呼ばれず、たしかに`MyViewModel`のインスタンスはシングルトンになります
- では "シングルトンな期間" はどこからどこまでなのでしょう？
  - これは **"Componentの生存期間"** です

### Componentの生存期間とは

- "`DaggerMyComponent.builder().build()` が呼ばれてから、Componentインスタンスから参照が外れるまで" です
- よって、例えば "アプリの生存期間内にずっとシングルトンを実現したい" 場合は、Componentを`Application`クラスに保持させる事でそれが実現できます
- 下記のコードはまさに `Application` に `Component` を管理させてますね

```java
public class MainActivity extends AppCompatActivity {

    @Inject
    MyViewModel myViewModel;

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

        ((MyApplication) getApplication()).getMyComponent().inject(this);
    }
```

### Scopeの役割

- "Componentの生存期間に名前を付けるもの" と考えると良さそうです
    - (多分正確には若干違うのかもしれないけど、解釈としてはズレてないはず)
- 下記の場合は "`MyComponent`の生存期間を `@Singleton` と名付ける" と解釈できます

```java

@Singleton
@Component(modules = { MyModule.class })
interface MyComponent {
    void inject(MainActivity activity);
    MySubComponent createMySubComponent();
}
```

#### `@Provider` に付随するscopeアノテーション

- provideするインスタンスの生存期間を指定します
    - よって下記の`MyViewModel`は`MyComponent`と同じ生存期間を持つことになります
    - `MyComponent` が生きている以上、`MyViewModel` はシングルトンですね

```java

@Module
class MyModule {
    @Provides
    MyFakeApi provideMyFakeApi() {
        return new MyFakeApi();
    }

    @Singleton
    @Provides
    MyViewModel provideMyViewModel(MyFakeApi api) {
        return new MyViewModel(api);
    }
}
```

- `@Singleton` という名前が、シングルトンであることと直接結びついていない事が分かります
- てことは `@Singleton` じゃなくても (scopeを表すアノテーションであれば) 同じ事が起きそうです

#### CustomScope

- Scopeは、下記のように自分で定義することも出来ます

```java

@Scope
@Retention(RetentionPolicy.RUNTIME)
@interface ViewModelScope {
}
```

- ここで注意したいのは `@ViewModelScope` と名前をつけたからといって、特に生存期間に対する効力は無いということです
- 例えば `Activity` のlifecycleと一致させたい！というのであれば、Componentの生存期間を `Activity` と一致させればよく、Scopeの名前や定義とは一切関係ないという事です
- 前述の例で `@Singleton` と置いていたところを `@ViewModelScope` に置き換えてもなんら動作上変わりません

### `@Provider` に付随させるscopeについて

- さて、先程挙げた例において `@Provider` に付随させていた `@Singleton`　を、下記のように `@ViewModelScope` へ変更させるとどうなるでしょう

```java

@Singleton
@Component(modules = { MyModule.class })
interface MyComponent {

    void inject(MainActivity activity);

    MySubComponent createMySubComponent();
}
```

```java

@Module
class MyModule {
    @Provides
    MyFakeApi provideMyFakeApi() {
        return new MyFakeApi();
    }

    @ViewModelScope
    @Provides
    MyViewModel provideMyViewModel(MyFakeApi api) {
        return new MyViewModel(api);
    }
}
```

- これはbuild時にerrorが出て (daggerのcompileに失敗する) buildが通りません
- 原因は 「`provideMyViewModel` メソッドからすると `@ViewModelScope` が、誰の (どのcomponentの) 生存期間なのか分からないため」 です
- `MyComponent` の `@Singleton` を `@ViewModelScope` にすることで、上記が解決してbuildが通るようになります

### こんなこと知ってて何になる?

- SubComponentを使う時に役立ちます (ブラックボックス感が軽減できる、という意味で)

#### SubComponent

- Dagger2には Component の依存を引き継いで定義ができる "SubComponent" というものがあります
- が、いざ使ってみると謎のトラブルが...、
    -  `@Singleton` をつけると急にbuildがコケる
    -  providerがinjectする度callされる (singletonに期待する事が機能しない) 

#### 困ったことが起こる原因

- [ここ(link)](https://github.com/google/dagger/issues/107#issuecomment-71073298)に解答が書いてありました
- "依存しあってるcomponentは同じscopeを共有できない" のだそうです
- よって "Componentに `@Singleton` がついていたら、SubComponentに `@Singleton` を付けるのはNGである" のは、納得ですね

#### 解決例

- "SubComopnentの生存期間内、特定のinstanceをシングルトンにしたい" のであれば、下記のように ComponentとSubComponentでscopeを変えてあげれば良いです
    - SubComponentで引っ張っているModuleのproviderのscopeが、SubComponentのscope (`@ViewModelScope`) になっている点に注意して下さい

```java

@Singleton
@Component(modules = { MyModule.class })
interface MyComponent {

    MySubComponent createMySubComponent();

}
```

```java

@ViewModelScope
@Subcomponent(modules = { MySubModule.class })
public interface MySubComponent {

    void inject(MainActivity activity);

}
```

```java

@Module
public class MySubModule {

    @ViewModelScope
    @Provides
    MyPojo provideMyPojo() {
        return new MyPojo("dance with the wild things");
    }
}
```

### じゃあScopeって何のためにあるんだろう

- もちろんDagger2をちゃんと動作させるために必要です
- あとは、Componentの生存期間をチームメンバで共有するためのラベルみたいなものとして考えるのもアリかなと思ってます
    - Activityより長くて、Applicationよりも短い生存期間がほしい時もある

### まとめ

- Dagger2のScopeの使い方をまとめました
- ブラックボックス感を拭えずに半年くらい使ってきたものの、それが晴れてスッキリしたので、みなさんと共有できればこれ幸いです

## 余談

### Daggerはlifecycle問題をクリアする手段なのか、議論

- 前述の "Componentのlifecycle" で触れましたが、結局Componentが破棄されればprovideされるインスタンスも全て破棄されます
    - ApplicationクラスでComponentを管理していれば、Applicationが生存する限りComponentに紐づくものはアプリが生きているうちは全てkeepされます
    - ActivityのonCreateでComponentをnewする実装にしていれば、Componentに紐づくもの全てのlifecycleも基本Activityと同じになります
- よって、**Scopeを如何に考慮しても、lifecycle云々の話はまた別の話 (Componentをどうやって管理するのか)**という結論に、自分の中では落ち着いています
    - http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/ この辺参考になります

### 気になっていること

- Providerのライフサイクルは、都度createか(Component生存期間内)シングルトンか、の2つしかないように見える
    - 本当？
    - もしかして手段があったりするんじゃないの？