株式会社FOLIOでAndroidエンジニアをしています@u_nationです。
この記事はFOLIOアドベントカレンダー15日目の記事です。昨日の記事はFOLIO最高の頭脳、lotzさんによるExtensible EffectsとTagless Finalで実装するDIでした。
本記事ではFlux+ViewModel(AAC)+Daggerの組み合わせで画面回転に対応する方法を紹介したいと思います。
画面回転に対応する方法について注力するので、Fluxについては詳しく言及致しません
※本記事内の言及している「ViewModel」はAndroid Acrchitecture ComponentsのViewModelになります。
はじめに
入社前、Fluxとはなんぞや?!状態の時にFlux de RelaxとFluxArchitectureSampleの二つの資料を読み込んだおかげでスムーズに仕事に入ることができました。感謝の意とリスペクトを込めて、今回オリジナルをフォークしてViewModel(AAC)導入+Kotlin化したサンプルを用意しました。
また本記事の98%のアイディアは画面回転を支えるデータ保持:)から拝借しております。
作者の@ogaclejapanさんには重ねて感謝申し上げます
TL;DR
1行まとめ
-
画面回転を支えるデータ保持:)で
onRetainCustomNonConfigurationInstance
によって解決していた部分をViewModelに置き換えてみた
3行まとめ
- FluxではデータをStoreで保持する。このStoreが持つデータを画面回転で破棄させないようにしたい。
- ViewModelで画面回転を生き抜くComponentを生成する。
- そのComponentから生成されたStoreは画面が破棄されるまで生き抜く。
本題
これまでDaggerと言えば、よくあるコンポーネントの階層構造は以下のようなものでした。
|--AppComponent
|--ActivityComponent
|--FragmentComponent
これはそのまま、各コンポーネントの生存期間がApplicationのライフサイクル、Activityのライフサイクル、Fragmentのライフサイクルと紐づいていることを現しています。
それがViewModelの登場で、我々は新たなライフサイクルを手に入れることになりました。1
よく見る図でわかるように、画面回転時のonDestroy
では破棄されず、finish()
等で画面が破棄されるタイミングまで生き延びてくれます。
このViewModelScopeを画面回転を支えるデータ保持:)にならってScreenScopeと命名し、その生存期間のコンポーネントをScreenComponentとします。
|--AppComponent
|--ScreenComponent
|--ActivityComponent
|--FragmentComponent
ScreenComponentの生存期間はViewModelのライフサイクルと同期します。それはすなわち、ScreenComponentから取得できるオブジェクトが画面回転を生き延びてくれることを意味します。
以下サンプルからコードを抜粋して説明していきます。
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のライフサイクルと一致します。
@ScreenScope
class UserSearchStore @Inject constructor(
dispatcher: Dispatcher,
screenLifecycleHook: ScreenLifecycleHook
) {
// 省略
}
Fluxでは通信処理で得たデータや、ユーザーが入力した情報等、UIに紐づくデータの管理をStore
で行います。
このStoreのデータを画面回転後も生き延びさせることによって、縦/横切り替え時データを再取得することなく、最適なUIを実現できます。
Storeには@ScreenScopeアノテーション
をつけて、コンストラクタインジェクションをさせます。こうすることで、StoreをScreenComponentのライフサイクルに紐付けできます。
これによってStore
は画面回転を生き抜いてくれるようになりました。╭( ・ㅂ・)و グッ !
良かったこと
- Activity,Fragment,AdapterのView側がViewModelの存在を知らなくていい。
- 任意でActivityScopeなStoreも生成できる。
- 同じStoreオブジェクトをDaggerからActivity,Fragment,Adapterに注入できる。最高(>_<)
おわりに
メモリリークが怖いので、変更通知を解放する拡張関数に@CheckResultアノテーション
をつける等、なるべく人的ミスを防ぐ工夫をしています。
また、LiveDataの導入も検討していて、うまくいけば現状の変更通知解放のためのロジックをシンプルにできるので、その際はまたシェアして行きたいと思います。╭( ・ㅂ・)و グッ !
お世話になった記事
明日の記事はインフラエンジニアyasuharu519氏による「監視システムの Push 型 Pull 型アプローチと Prometheus についての考察」です!