Flux+ViewModel(AAC)+Daggerで画面回転対応してみた

株式会社FOLIOでAndroidエンジニアをしています[@u_nation](https://twitter.com/u_nation)です。
この記事はFOLIOアドベントカレンダー１５日目の記事です。昨日の記事はFOLIO最高の頭脳、[lotzさん](https://qiita.com/lotz)による[Extensible EffectsとTagless Finalで実装するDI](https://qiita.com/lotz/items/a903d3b2aec0c1d4f3ce)でした。

本記事では**Flux+ViewModel(AAC)+Dagger**の組み合わせで**画面回転に対応する方法**を紹介したいと思います。  
画面回転に対応する方法について注力するので、Fluxについては詳しく言及致しません:bow:

*※本記事内の言及している「ViewModel」はAndroid Acrchitecture ComponentsのViewModelになります。*

---

## <font color="#18bac5">はじめに</font>
入社前、Fluxとはなんぞや？！状態の時に[Flux de Relax](https://speakerdeck.com/ogaclejapan/flux-de-relax)と[FluxArchitectureSample](https://github.com/ogaclejapan/FluxArchitectureSample)の二つの資料を読み込んだおかげでスムーズに仕事に入ることができました。感謝の意とリスペクトを込めて、今回オリジナルをフォークしてViewModel(AAC)導入＋Kotlin化した[サンプル](https://github.com/u-nation/FluxArchitectureSample)を用意しました。

また本記事の98％のアイディアは[画面回転を支えるデータ保持:)](https://speakerdeck.com/ogaclejapan/how-to-keep-data-between-orientation-changes)から拝借しております。

作者の[@ogaclejapan](https://twitter.com/ogaclejapan)さんには重ねて感謝申し上げます:bow:

## <font color="#18bac5">TL;DR</font>

##### 1行まとめ
- [画面回転を支えるデータ保持:)](https://speakerdeck.com/ogaclejapan/how-to-keep-data-between-orientation-changes)で`onRetainCustomNonConfigurationInstance`によって解決していた部分をViewModelに置き換えてみた

##### 3行まとめ

- Fluxではデータを**Store**で保持する。このStoreが持つデータを画面回転で破棄させないようにしたい。
- ViewModelで画面回転を生き抜くComponentを生成する。
- そのComponentから生成されたStoreは画面が破棄されるまで生き抜く。


## <font color="#18bac5">本題</font>

これまでDaggerと言えば、よくあるコンポーネントの階層構造は以下のようなものでした。

```
|--AppComponent
    |--ActivityComponent
        |--FragmentComponent
```

これはそのまま、各コンポーネントの生存期間がApplicationのライフサイクル、Activityのライフサイクル、Fragmentのライフサイクルと紐づいていることを現しています。

それがViewModelの登場で、我々は新たなライフサイクルを手に入れることになりました。[^1]  
よく見る図でわかるように、画面回転時の`onDestroy`では破棄されず、`finish()`等で画面が破棄されるタイミングまで生き延びてくれます。

<img src=https://qiita-image-store.s3.amazonaws.com/0/58939/5b1451e6-a6f1-3403-943f-623d82d850ef.png width=400px> 

このViewModelScopeを[画面回転を支えるデータ保持:)](https://speakerdeck.com/ogaclejapan/how-to-keep-data-between-orientation-changes)にならって**ScreenScope**と命名し、その生存期間のコンポーネントを**ScreenComponent**とします。

```
|--AppComponent
    |--ScreenComponent
        |--ActivityComponent
            |--FragmentComponent
```

ScreenComponentの生存期間はViewModelのライフサイクルと同期します。それはすなわち、**ScreenComponentから取得できるオブジェクトが画面回転を生き延びてくれることを意味します。**

---

以下[サンプル](https://github.com/u-nation/FluxArchitectureSample)からコードを抜粋して説明していきます。


```kotlin
class ScreenViewModel(application: Application) : AndroidViewModel(application) {

  private val screenLifecycle: ScreenLifecycleHook = ScreenLifecycleHook()

  val screenComponent: ScreenComponent =
      Components.forApp(application).plus(ScreenModule(screenLifecycle))

  init {
    screenLifecycle.dispatchOnInit()
  }

  override fun onCleared() {
    screenLifecycle.dispatchOnCleared()
  }
}
```

ScreenComponentの生成はScreenViewModelが行います。これで、ScreenComponentの生存期間がViewModelのライフサイクルと一致します。

```kotlin
@ScreenScope
class UserSearchStore @Inject constructor(
    dispatcher: Dispatcher,
    screenLifecycleHook: ScreenLifecycleHook
) {
  // 省略
}
```

Fluxでは通信処理で得たデータや、ユーザーが入力した情報等、UIに紐づくデータの管理を`Store`で行います。
このStoreのデータを画面回転後も生き延びさせることによって、縦/横切り替え時データを再取得することなく、最適なUIを実現できます。

Storeには`@ScreenScopeアノテーション`をつけて、コンストラクタインジェクションをさせます。こうすることで、StoreをScreenComponentのライフサイクルに紐付けできます。

これによって`Store`は画面回転を生き抜いてくれるようになりました。╭( ･ㅂ･)و ｸﾞｯ !

###### <font color="#2196f3">良かったこと</font>
- Activity,Fragment,AdapterのView側がViewModelの存在を知らなくていい。
- 任意でActivityScopeなStoreも生成できる。
- 同じStoreオブジェクトをDaggerからActivity,Fragment,Adapterに注入できる。最高(>_<)

## <font color="#18bac5">おわりに</font>
メモリリークが怖いので、変更通知を解放する拡張関数に`@CheckResultアノテーション`をつける等、なるべく人的ミスを防ぐ工夫をしています。  

また、**LiveData**の導入も検討していて、うまくいけば現状の変更通知解放のためのロジックをシンプルにできるので、その際はまたシェアして行きたいと思います。╭( ･ㅂ･)و ｸﾞｯ !

[^1]: [ViewModelの実体は古の`setRetainInstance(true)`なUIなしFragment](http://sys1yagi.hatenablog.com/entry/2017/08/22/221721)

## <font color="#18bac5">お世話になった記事</font>
- [画面回転を支えるデータ保持:)](https://speakerdeck.com/ogaclejapan/how-to-keep-data-between-orientation-changes)
- [Flux de Relax](https://speakerdeck.com/ogaclejapan/flux-de-relax)
- [読むと怖くないDagger2](https://www.slideshare.net/shinnosukekugimiya/dagger2-52493651)
- [Dagger2のscopeの使い方を正しく理解する](https://qiita.com/tsuyoyo/items/50edacbb544e383dac93)

---

明日の記事はインフラエンジニア[yasuharu519](https://qiita.com/yasuharu519)氏による「監視システムの Push 型 Pull 型アプローチと Prometheus についての考察」です！