Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

株式会社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 についての考察」です!

u_nation
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした