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<List<User>>().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. // Use the 'by viewModels()' Kotlin property delegate // from the activity-ktx artifact val model: MyViewModel by viewModels() 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 = new ViewModelProvider(this).get(MyViewModel.class); model.getUsers().observe(this, users -> { // update UI }); } }
再作成されたアクティビティが受け取る MyViewModel
インスタンスは、最初のアクティビティで作成されたものと同じです。オーナーのアクティビティが終了すると、フレームワークは、リソースをクリーンアップできるように ViewModel
オブジェクトの onCleared()
メソッドを呼び出します。
ViewModel
オブジェクトは、ビューや LifecycleOwners
の特定のインスタンスよりも生存期間が長くなるように設計されています。この設計では、ViewModel
を対象とするテストを簡単に作成することもできます。このオブジェクトでは、ビュー オブジェクトと Lifecycle
オブジェクトが認識されないためです。ViewModel
オブジェクトには LifecycleObservers
(LiveData
オブジェクトなど)を含めることができます。ただし、ViewModel
オブジェクトでは、ライフサイクル対応の監視可能オブジェクト(LiveData
オブジェクトなど)への変更を監視しないでください。ViewModel
で Application
のコンテキストが必要な場合は(システム サービスを検出する場合など)、AndroidViewModel
クラスを拡張して、Application
を受け取るコンストラクタを作成できます(Application
クラスは Context
を拡張したものであるため)。
ViewModel のライフサイクル
ViewModel
オブジェクトのスコープは、ViewModel
を取得する際に、ViewModelProvider
に渡される Lifecycle
に設定されます。ViewModel
は、スコープの Lifecycle
が完全に消えるまで(アクティビティの場合は終了時、フラグメントの場合はデタッチ時)メモリ内に残ります。
図 1 に、回転を経て終了するまでのアクティビティのさまざまなライフサイクルの状態を示します。この図には、関連するアクティビティのライフサイクルの横に ViewModel
のライフタイムも示されています。この図はアクティビティの状態を示していますが、フラグメントのライフサイクルの状態も基本的には同じです。
通常は、アクティビティ オブジェクトの onCreate()
メソッドが最初に呼び出されたときに、ViewModel
をリクエストします。onCreate()
は、デバイスの画面が回転されたときなど、アクティビティの生存期間全体を通して複数回呼び出されることがあります。ViewModel
は、最初に ViewModel
をリクエストしてからアクティビティが終了して破棄されるまで存在します。
フラグメント間でデータを共有する
よくあるのは、アクティビティ内の複数のフラグメントが互いにやり取りする必要があるケースです。一方のフラグメントでリスト内のアイテムを選択し、もう一方のフラグメントで選択したアイテムのコンテンツを表示する、一般的な分割ビュー(list-detail
)のフラグメントのケースについて考えてみます。このケースは決して簡単ではありません。両方のフラグメントがインターフェースの記述を定義する必要があり、オーナーのアクティビティは、この 2 つのフラグメントを互いにバインドする必要があるためです。また、どちらのフラグメントも、もう一方のフラグメントがまだ作成されていないシナリオ、または参照できないシナリオに対処する必要があります。
この一般的な問題に対処するには、ViewModel
オブジェクトを使用します。次のサンプルコードに示すように、これらのフラグメントではアクティビティ スコープを使って ViewModel
を共有し、このやり取りを処理できます。
Kotlin
class SharedViewModel : ViewModel() { val selected = MutableLiveData<Item>() fun select(item: Item) { selected.value = item } } class ListFragment : Fragment() { private lateinit var itemSelector: Selector // Use the 'by activityViewModels()' Kotlin property delegate // from the fragment-ktx artifact private val model: SharedViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) itemSelector.setOnClickListener { item -> // Update the UI } } } class DetailFragment : Fragment() { // Use the 'by activityViewModels()' Kotlin property delegate // from the fragment-ktx artifact private val model: SharedViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) model.selected.observe(viewLifecycleOwner, 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 ListFragment extends Fragment { private SharedViewModel model; public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class); itemSelector.setOnClickListener(item -> { model.select(item); }); } } public class DetailFragment extends Fragment { public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class); model.getSelected().observe(getViewLifecycleOwner(), item -> { // Update the UI. }); } }
両方のフラグメントが、それらを含むアクティビティを取得することに注意してください。このように、それぞれのフラグメントが ViewModelProvider
を取得するとき、スコープがアクティビティに設定された同じ SharedViewModel
インスタンスを受け取ります。
この方法には次のようなメリットがあります。
- アクティビティは何もする必要がなく、フラグメント間のやり取りについて何も認識する必要がありません。
- フラグメントは、
SharedViewModel
コントラクト以外は互いについて認識する必要がありません。フラグメントの一方が存在しなくなっても、もう一方は通常どおり処理を継続します。 - フラグメントはそれぞれ独自のライフサイクルを持ち、もう一方のフラグメントのライフサイクルによる影響を受けません。2 つのフラグメントが入れ替わっても、UI は何の問題もなく処理を継続します。
ローダを ViewModel に置き換える
CursorLoader
などのローダクラスを頻繁に使用すると、アプリの UI のデータとデータベースの同期を維持できます。ViewModel
を他のいくつかのクラスと組み合わせて使用することで、ローダの代わりにできます。ViewModel
を使用すると、UI コントローラをデータ読み込み処理から分離できます。これにより、クラス間の強い参照を減らすことができます。
ローダを使用する一般的なアプローチの 1 つとして、アプリで CursorLoader
を使用してデータベースのコンテンツを監視することがあります。データベース内の 1 つの値が変更されると、ローダがデータの再読み込みを自動的にトリガーして UI を更新します。

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

ViewModel でコルーチンを使用する
ViewModel
には、Kotlin コルーチンのサポートが含まれています。詳しくは、Android アーキテクチャ コンポーネントで Kotlin コルーチンを使用するをご覧ください。
追加情報
データの複雑さが増すと、データの読み込みのためだけに別のクラスを使用することがあります。ViewModel
の目的は、UI コントローラのデータをカプセル化して、構成の変更後にもデータが引き継がれるようにすることです。構成の変更の前後におけるデータの読み込み、永続化、管理の方法については、UI の状態の保存をご覧ください。
Android アプリのアーキテクチャ ガイドでは、これらの機能を処理するためにリポジトリ クラスを作成することが推奨されています。
参考情報
ViewModel
クラスについて詳しくは、以下のリソースをご覧ください。
サンプル
- Android アーキテクチャ コンポーネントの基本的なサンプル
- Sunflower(Android Jetpack を使用した Android 開発のおすすめの方法を示すガーデニング アプリ)
Codelab
- Android Room とビュー(Java)、(Kotlin)
- Android ライフサイクル対応コンポーネントの Codelab
ブログ
- ViewModel: 簡単な例
- ViewModel: 永続性、onSaveInstanceState()、UI の状態の復元、ローダ
- ViewModel と LiveData: パターンとアンチパターン
- Kotlin の詳細: 簡単なラムダ式の構文を理解する
- Kotlin の詳細: スコープ関数
- Kotlin の詳細: カスタム アクセサーを使用するタイミング
- アーキテクチャ コンポーネントを使用したライフサイクル対応データの読み込み