ViewModel の概要  Android Jetpack の一部

ViewModel は、ライフサイクルを意識した方法で UI 関連のデータを保存および管理するためのクラスです。ViewModel クラスを使用すると、画面の回転などの設定の変更後にデータを引き継ぐことができます。

アクティビティやフラグメントなどの UI コントローラのライフサイクルは Android フレームワークによって管理されます。制御できない特定のユーザーの操作やデバイス イベントへの対応として、フレームワークが UI コントローラの破棄や再作成を行うよう決定する場合があります。

UI コントローラが破棄または再作成されると、UI コントローラに保存されている一時的な UI 関連のデータはすべて失われます。たとえば、アプリではそのアクティビティのいずれかにユーザーのリストを含める場合があります。設定の変更によってアクティビティが再作成されると、その新しいアクティビティはユーザーのリストを再取得する必要があります。単純なデータの場合は、アクティビティが onSaveInstanceState() メソッドを使用して onCreate() のバンドルからデータを復元できますが、このアプローチが適しているのは、シリアル化してからシリアル化解除することができる少量のデータの場合だけです。ユーザーやビットマップのリストのようにデータの量が多くなる可能性がある場合には適していません。

もう 1 つの問題は、UI コントローラでは実行に時間がかかる非同期呼び出しを頻繁に行う必要があることです。UI コントローラは非同期呼び出しを管理し、破棄された後にシステムが呼び出しのクリーンアップを行ってメモリリークが発生しないようにする必要があります。この管理ではメンテナンスを何度も実施する必要があり、設定の変更によってオブジェクトが再作成された場合にはすでに行った呼び出しを再度行わなければならないこともあるため、リソースが無駄になります。

アクティビティやフラグメントなどの UI コントローラの主な目的は、UI データの表示、ユーザーの操作への対処、オペレーティング システムとのやり取り(権限のリクエストなど)を行うことです。UI コントローラに対してデータベースやネットワークからのデータ読み込みも行うよう要求すると、クラスが肥大化することになります。UI コントローラに過度の役割を割り当てると、アプリの作業を他のクラスに任せずに 1 つのクラスですべて処理しようとすることになり、テストも困難になります。

ビューデータの所有権を UI コントローラのロジックから切り離すことで、複雑さが軽減され、効率性が高まります。

ViewModel を実装する

アーキテクチャ コンポーネントには、UI コントローラ向けに UI のデータを準備する ViewModel ヘルパークラスが用意されています。ViewModel オブジェクトは設定の変更時に自動的に保持されるため、このオブジェクトが保持しているデータは後続のアクティビティやフラグメントのインスタンスがすぐに利用できます。たとえば、アプリでユーザーのリストを表示する必要がある場合は、次のサンプルコードに示すように、ユーザーのリストを取得して保持する役割をアクティビティやフラグメントではなく ViewModel に割り当てます。

Kotlin

    class MyViewModel : ViewModel() {
        private val users: MutableLiveData<List<User>> by lazy {
            MutableLiveData().also {
                loadUsers()
            }
        }

        fun getUsers(): LiveData<List<User>> {
            return users
        }

        private fun loadUsers() {
            // Do an asynchronous operation to fetch users.
        }
    }
    

Java

    public class MyViewModel extends ViewModel {
        private MutableLiveData<List<User>> users;
        public LiveData<List<User>> getUsers() {
            if (users == null) {
                users = new MutableLiveData<List<User>>();
                loadUsers();
            }
            return users;
        }

        private void loadUsers() {
            // Do an asynchronous operation to fetch users.
        }
    }
    

次のように、リストにはアクティビティからアクセスできます。

Kotlin

    class MyActivity : AppCompatActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {
            // Create a ViewModel the first time the system calls an activity's onCreate() method.
            // Re-created activities receive the same MyViewModel instance created by the first activity.

            val model = ViewModelProviders.of(this)[MyViewModel::class.java]
            model.getUsers().observe(this, Observer<List<User>>{ users ->
                // update UI
            })
        }
    }
    

Java

    public class MyActivity extends AppCompatActivity {
        public void onCreate(Bundle savedInstanceState) {
            // Create a ViewModel the first time the system calls an activity's onCreate() method.
            // Re-created activities receive the same MyViewModel instance created by the first activity.

            MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
            model.getUsers().observe(this, users -> {
                // update UI
            });
        }
    }
    

再作成されたアクティビティが受け取る MyViewModel インスタンスは、最初のアクティビティで作成されたものと同じです。オーナーのアクティビティが終了すると、フレームワークは、リソースをクリーンアップできるように ViewModel オブジェクトの onCleared() メソッドを呼び出します。

ViewModel オブジェクトは、ビューや LifecycleOwners の特定のインスタンスよりも生存期間が長くなるように設計されています。この設計では、ViewModel を対象とするテストを簡単に作成することもできます。このオブジェクトでは、ビュー オブジェクトおよび Lifecycle オブジェクトが認識されないためです。 ViewModel オブジェクトには LifecycleObserversLiveData オブジェクトなど)を含めることができます。ただし、ViewModel オブジェクトでは、ライフサイクル対応の監視可能オブジェクト(LiveData オブジェクトなど)への変更を監視しないでください。ViewModelApplication のコンテキストが必要な場合は(システム サービスを検出する場合など)、AndroidViewModel クラスを拡張して、Application を受け取るコンストラクタを作成できます(Application クラスは Context を拡張したものであるため)。

ViewModel のライフサイクル

ViewModel オブジェクトのスコープは、ViewModel を取得する際に、ViewModelProvider に渡される Lifecycle に設定されます。ViewModel は、スコープの Lifecycle が完全に消えるまで(アクティビティの場合は終了時、フラグメントの場合はデタッチ時)メモリ内に残ります。

図 1 に、回転を行ってから終了するまでのアクティビティのさまざまなライフサイクルの状態を示します。この図には、関連するアクティビティのライフサイクルの横に ViewModel のライフタイムも示されています。この図はアクティビティの状態を示していますが、フラグメントのライフサイクルの状態も基本的には同じです。

ViewModel のライフサイクルをアクティビティの状態の変化とともに図で示します。

通常は、アクティビティ オブジェクトの onCreate() メソッドが最初に呼び出されたときに、ViewModel をリクエストします。onCreate() は、デバイスの画面が回転されたときなど、アクティビティの生存期間全体を通して複数回呼び出されることがあります。ViewModel は、最初に ViewModel をリクエストしてからアクティビティが終了して破棄されるまで存在します。

フラグメント間でデータを共有する

よくあるのは、アクティビティ内の複数のフラグメントが互いにやり取りする必要があるケースです。一方のフラグメントでリスト内のアイテムを選択し、もう一方のフラグメントで選択したアイテムのコンテンツを表示する、一般的なマスター / ディテール関係にあるフラグメントのケースについて考えてみます。このケースは決して簡単ではありません。両方のフラグメントがインターフェースの記述を定義する必要があり、オーナーのアクティビティは、この 2 つのフラグメントを互いにバインドする必要があるためです。また、どちらのフラグメントも、もう一方のフラグメントがまだ作成されていないシナリオ、または参照できないシナリオに対処する必要があります。

この一般的な問題に対処するには、ViewModel オブジェクトを使用します。次のサンプルコードに示すように、これらのフラグメントではアクティビティ スコープを使って ViewModel を共有し、このやり取りを処理できます。

Kotlin

    class SharedViewModel : ViewModel() {
        val selected = MutableLiveData<Item>()

        fun select(item: Item) {
            selected.value = item
        }
    }

    class MasterFragment : Fragment() {

        private lateinit var itemSelector: Selector

        private lateinit var model: SharedViewModel

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            model = activity?.run {
                ViewModelProviders.of(this)[SharedViewModel::class.java]
            } ?: throw Exception("Invalid Activity")
            itemSelector.setOnClickListener { item ->
                // Update the UI
            }
        }
    }

    class DetailFragment : Fragment() {

        private lateinit var model: SharedViewModel

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            model = activity?.run {
                ViewModelProviders.of(this)[SharedViewModel::class.java]
            } ?: throw Exception("Invalid Activity")
            model.selected.observe(this, Observer<Item> { item ->
                // Update the UI
            })
        }
    }
    

Java

    public class SharedViewModel extends ViewModel {
        private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

        public void select(Item item) {
            selected.setValue(item);
        }

        public LiveData<Item> getSelected() {
            return selected;
        }
    }

    public class MasterFragment extends Fragment {
        private SharedViewModel model;
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
            itemSelector.setOnClickListener(item -> {
                model.select(item);
            });
        }
    }

    public class DetailFragment extends Fragment {
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
            model.getSelected().observe(this, { item ->
               // Update the UI.
            });
        }
    }
    

両方のフラグメントが、それらを含むアクティビティを取得することに注意してください。このように、それぞれのフラグメントが ViewModelProvider を取得するとき、スコープがアクティビティに設定された同じ SharedViewModel インスタンスを受け取ります。

この方法には次のようなメリットがあります。

  • アクティビティは何もする必要がなく、フラグメント間のやり取りについて何も認識する必要がありません。
  • フラグメントは、SharedViewModel コントラクト以外は互いについて認識する必要がありません。フラグメントの一方が存在しなくなっても、もう一方は通常どおり処理を継続します。
  • フラグメントはそれぞれ独自のライフサイクルを持ち、もう一方のフラグメントのライフサイクルによる影響を受けません。2 つのフラグメントが入れ替わっても、UI は何の問題もなく処理を継続します。

ローダーを ViewModel に置き換える

CursorLoader などのローダークラスを頻繁に使用すると、アプリの UI のデータとデータベースの同期を維持することができます。ViewModel を他のいくつかのクラスと組み合わせて使用することで、ローダーの代わりにできます。ViewModel を使用すると、UI コントローラをデータ読み込み処理から分離できます。これにより、クラス間の強い参照を減らすことができます。

ローダーを使用する一般的なアプローチの 1 つとして、アプリで CursorLoader を使用してデータベースのコンテンツを監視することがあります。データベース内の 1 つの値が変更されると、ローダーがデータの再読み込みを自動的にトリガーして UI を更新します。

図 2. ローダーを使用したデータの読み込み

ViewModelRoomLiveData と組み合わせて使用することで、ローダーの代わりにできます。ViewModel を使用すると、デバイスの設定の変更後にもデータが引き継がれるようになります。 Room は、データベースが変更されたときに LiveData に通知します。LiveData はそれを受けて、変更されたデータを使用して UI を更新します。

図 3. ViewModel を使用したデータの読み込み

ViewModel でコルーチンを使用する

ViewModel には、Kotlin コルーチンのサポートが含まれています。詳しくは、Android アーキテクチャ コンポーネントで Kotlin コルーチンを使用するをご覧ください。

追加情報

データの複雑さが増すと、データの読み込みのためだけに別のクラスを使用することがあります。ViewModel の目的は、UI コントローラのデータをカプセル化して、設定の変更後にもデータが引き継がれるようにすることです。設定の変更の前後におけるデータの読み込み、永続化、管理の方法については、UI の状態の保存をご覧ください。

Android アプリのアーキテクチャ ガイドでは、これらの機能を処理するためにリポジトリ クラスを作成することが推奨されています。

その他のリソース

ViewModel クラスについて詳しくは、以下のリソースをご覧ください。

サンプル

コードラボ

ブログ

動画