【和訳】Android Architecture Components ViewModel

自分で理解するために ViewModel | Android Developers (原文) を訳しました。

サンプルコードや図を全て写していないので、原文を見ながら適宜理解を深めるのに利用してください。また、理解しやすいよう所々補足を加えています。先にアクティビティの実行時の変更処理について理解しておくと読みやすくなります。

 

ViewModel

ViewModelクラスはUIに関連するデータの保持と管理をするようデザインされています。そのため画面回転などの設定変更があってもデータは保持されます。

NOTE: ViewModelをAndroidプロジェクトにインポートするには、adding components to your project を見てください。

アクティビティやフラグメントなどのAppコンポーネントAndroidフレームワークによって管理されるライフサイクルを持ちます。
Androidフレームワークは、開発者がコントロールできないユーザーのアクションや端末のイベントに従って、ActivityやFragmentを破棄または再生成するか決めます。
Activity/FragmentのオブジェクトがOSによって破棄または再生成されると、それらが保持していた全てのデータは破棄されます。
例えば、ユーザーのリスト(List<User>)をActivityが持っていて、設定変更によってActivityが再生成されると、新しく生成されたActivityはリストデータを取得し直さなければなりません。
データが単純ならActivity#onSaveInstanceState()メソッドを使って、Activity#onCreate()でBundleからデータを修復することができます。
しかしこの方法は、UIの状態など少量のデータに向いています。サイズが大きくなりそうなユーザーのリスト(List<User>)には向いていません。

他にも問題があります。activityやfragmentなどのUIコントローラーは戻るまでに時間のかかる非同期の処理を頻繁に呼ぶ必要があります。
UIコントローラーはそれらの非同期処理を管理する必要があります。非同期処理が中断されたら、メモリリークを避けるためそれらを片付ける必要があります。
これは多くの管理を必要とします。また、オブジェクトが設定変更によって再生成されて、同じ処理が呼ばれることはリソースの無駄使いです。

最後にこれも重要なことですが、これらのUIコントローラーはユーザーのアクションに反応したり、OSとのコミュニケーションに対応する必要があります。
そして自身が持つリソースも管理することになると、クラスは膨れ上がります。
そのような状態は、1つのクラスがアプリのすべての機能を他のクラスに委譲せずに自分自身でやろうとすることになります。このような状態はテストを難しくします。

Viewに必要なデータのオーナーシップをUIコントローラーのロジックから分離するのが簡単で効果的です。
ライフサイクルパッケージはViewModelと呼ばれる新しいクラスを提供します。これはUIコントローラー(Activity/Fragment)のためのヘルパークラスで、UIに使うデータを準備する役割を担います。
ViewModelは設定変更があっても自動的に保持されます。そのため、ViewModelが持つデータは再生成されたActivity/Fragmentですぐに利用できます。

前述しましたが、ユーザーのリスト(List<User>)を取得し保持するのはActivityやFragmentではなく、ViewModelの責任であるべきです。
もしActivityが再生成されても、再生成された新しいActivityは、前のActivityによって作られたものと同じViewModelのインスタンスを受け取ります。
Activityが破棄されると、AndroidフレームワークはViewModel#onCleared()を呼び、ViewModelのインスタンスは破棄されます。

NOTE: Activity/Fragmentの初期化時にもViewModelは生き残ることから、ViewModelはViewを参照するべきではありません。
またどんなクラスでもそれがActivityコンテクストの参照を持つ場合は、そのクラスを参照すべきではありません。
もしViewModelがApplicationコンテクストを必要とする場合は、提供されているAndroidViewModelを継承して、コンストラクタでApplicationを受け取ってください。(ApplicationクラスはContextを継承しています)

 

Sharing Data Between Fragments

2つ以上のフラグメントが1つのアクティビティの中で、連携を取り合うことはよくあることです。
2つのフラグメントがインタフェースを定義し、オーナーであるactivityが2つを結ぶことは、珍しいことではありません。
さらに2つのフラグメントはお互いが存在するか、visibleな状態であるか考えなければなりません。
このよくある問題は、ViewModelオブジェクトを使って解決できます。
よくあるmaster-detail fragmentsのケースを想像してください。アイテムリストを表示するfragmentからユーザーがアイテムを1つ選ぶと、選んだアイテムの詳細を表示するfragmentが出てくるパターンです。
この2つのfragmentは、Activityのスコープを使って、お互いのコミュニケーションを管理するためにViewModelを共有することができます。

gistbd67d578044e830ee00943b0e43145e7

2つのfragmentがVIewModelProviderを利用する際、getActivity()を使っていることに注意してください。これは2つのFragmentが同じactivityの範囲で、SharedViewModelのインスタンスを受け取ることを意味します。

このアプローチの利点:

  • Activityは何もする必要がありません。fragmentsのコミュニケーションについて知る必要もありません。
  • FragmentはViewModel以外、お互いを一切知る必要がありません。片方が消えても、もう片方はいつも通り動き続けます。
  • 各Fragmentはそれぞれのライフサイクルを持ちます。もう片方のライフサイクルの影響を受けることもありません。実際、Fragmentが他のFragmentに入れ替わっている間も、UIは問題なく機能します。

ViewModelの機能は、ViewModelを取得する際ViewModelProviderに渡したライフサイクルの範囲で利用できます。(activityを渡したら、activityがfinish()されるまで利用できます。)
ViewModelは、ライフサイクルが終了するまでメモリに置かれます。activityならActivity#finish()、fragmentならFragment#detached()が呼ばれるまで、メモリに残ります。

 

ViewModel vs SavedInstanceState

ViewModelは、設定変更があってもデータを保ち続けるための簡単な方法を提供します。しかし、アプリケーションがOSによってkillされるときは破棄されます。
例えば、ユーザーがアプリを離れて、数時間後に戻ってきたとき、すでにプロセスは終了され、Android OSが保存されたデータからActivityを復元しています。
すべてのframeworkコンポーネント(views, activities, fragments)は、saved instance state機能を使って状態を保存します。
ほとんどの場合、開発者は何もする必要がありません。開発者はonSavedInstanceStateコールバックを使ってデータをbundleに追加するだけです。

onSavedInstanceStateを使って保存されたデータは、システムのプロセスメモリに保持されます。そして、Android OSは開発者にとても少ない量のデータを保持することを許可しています。
そのため、実際のデータ(アプリが使うコンテンツデータ等)を保持させるのに適した場所ではありません。
開発者は、UIコンポーネントによって簡単に置き換えられないデータのためにプロセスメモリを使うのは控えましょう。
例えば、Country情報を見せるユーザーインターフェースがあっても、絶対にCountryオブジェクトをsaved instance stateに入れないでください。countryIdを入れることはできます。(ViewやFragment argumentにすでに保存されていなければ) 実際に利用するオブジェクトはデータベースに入るべきです。そしてViewModelが保存されたcountryIdを使って、オブジェクトを取得します。