20
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

FOLIOAdvent Calendar 2017

Day 15

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

Last updated at Posted at 2017-12-15

株式会社FOLIOでAndroidエンジニアをしています@u_nationです。
この記事はFOLIOアドベントカレンダー15日目の記事です。昨日の記事はFOLIO最高の頭脳、lotzさんによるExtensible EffectsとTagless Finalで実装するDIでした。

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

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


はじめに

入社前、Fluxとはなんぞや?!状態の時にFlux de RelaxFluxArchitectureSampleの二つの資料を読み込んだおかげでスムーズに仕事に入ることができました。感謝の意とリスペクトを込めて、今回オリジナルをフォークしてViewModel(AAC)導入+Kotlin化したサンプルを用意しました。

また本記事の98%のアイディアは画面回転を支えるデータ保持:)から拝借しております。

作者の@ogaclejapanさんには重ねて感謝申し上げます:bow:

TL;DR

1行まとめ
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 についての考察」です!

  1. ViewModelの実体は古のsetRetainInstance(true)なUIなしFragment

20
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?