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 に割り当てます。

View

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.
    }
}

View

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.
    }
}

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

View

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
        })
    }
}

View

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 オブジェクトには LifecycleObserversLiveData オブジェクトなど)を含めることができます。ただし、ViewModel オブジェクトでは、ライフサイクル対応の監視可能オブジェクト(LiveData オブジェクトなど)への変更を監視しないでください。ViewModelApplication のコンテキストが必要な場合は(システム サービスを検出する場合など)、AndroidViewModel クラスを拡張して、Application を受け取るコンストラクタを作成できます(Application クラスは Context を拡張したものであるため)。

依存関係を使用して ViewModel を作成する

依存関係注入のベスト プラクティスに基づいて、ViewModel はコンストラクタのパラメータとして依存関係を取得できます。ほとんどの場合、これらはドメインレイヤまたはデータレイヤのタイプです。フレームワークは ViewModel を提供しているため、ViewModel のインスタンスを作成するには特別なメカニズムが必要です。そのメカニズムとは、ViewModelProvider.Factory インターフェースです。このインターフェースの実装のみが、適切なスコープで ViewModel をインスタンス化することができます

ViewModel クラスがコンストラクタ内で依存関係を受け取る場合は、ViewModelProvider.Factory インターフェースを実装するファクトリを提供してください。create(Class<T>, CreationExtras) 関数をオーバーライドして、ViewModel の新しいインスタンスを提供します。

CreationExtras を使用すると、ViewModel のインスタンス化に役立つ関連情報にアクセスできます。エクストラからアクセスできるキーのリストは次のとおりです。

キー 機能
ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY この Key により、ViewModelProvider.get() に渡したカスタムキーにアクセスできます。
ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY Application クラスのインスタンスにアクセスできます。
SavedStateHandleSupport.DEFAULT_ARGS_KEY SavedStateHandle の作成に使用する引数のバンドルにアクセスできます。
SavedStateHandleSupport.SAVED_STATE_REGISTRY_OWNER_KEY ViewModel の作成に使用されている SavedStateRegistryOwner にアクセスできます。
SavedStateHandleSupport.VIEW_MODEL_STORE_OWNER_KEY ViewModel の作成に使用されている ViewModelStoreOwner にアクセスできます。

SavedStateHandle の新しいインスタンスを作成するには、CreationExtras.createSavedStateHandle().createSavedStateHandle() 関数を使用して ViewModel に渡します。

次の例は、Application クラスをスコープとするリポジトリSavedStateHandle を依存関係として取得する ViewModel のインスタンスを提供する方法を示しています。

View

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.CreationExtras

class MyViewModel(
    private val myRepository: MyRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    // ViewModel logic
    // ...

    // Define ViewModel factory in a companion object
    companion object {

        val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
            @Suppress("UNCHECKED_CAST")
            override fun <T : ViewModel> create(
                modelClass: Class<T>,
                extras: CreationExtras
            ): T {
                // Get the Application object from extras
                val application = checkNotNull(extras[APPLICATION_KEY])
                // Create a SavedStateHandle for this ViewModel from extras
                val savedStateHandle = extras.createSavedStateHandle()

                return MyViewModel(
                    (application as MyApplication).myRepository,
                    savedStateHandle
                ) as T
            }
        }
    }
}

View

import static androidx.lifecycle.SavedStateHandleSupport.createSavedStateHandle;
import static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY;

import androidx.lifecycle.SavedStateHandle;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.viewmodel.ViewModelInitializer;

public class MyViewModel extends ViewModel {

    public MyViewModel(
        MyRepository myRepository,
        SavedStateHandle savedStateHandle
    ) { /* Init ViewModel here */ }

    static final ViewModelInitializer<MyViewModel> initializer = new ViewModelInitializer<>(
        MyViewModel.class,
        creationExtras -> {
            MyApplication app = (MyApplication) creationExtras.get(APPLICATION_KEY);
            assert app != null;
            SavedStateHandle savedStateHandle = createSavedStateHandle(creationExtras);

            return new MyViewModel(app.getMyRepository(), savedStateHandle);
        }
    );
}

次に、ViewModel のインスタンスを取得するときに、このファクトリを使用できます。

View

import androidx.activity.viewModels

class MyActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels { MyViewModel.Factory }

    // Rest of Activity code
}

View

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;

public class MyActivity extends AppCompatActivity {

    MyViewModel myViewModel = new ViewModelProvider(
        this,
        ViewModelProvider.Factory.from(MyViewModel.initializer)
    ).get(MyViewModel.class);

    // Rest of Activity code
}

Compose

import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun MyScreen(
    modifier: Modifier = Modifier,
    viewModel: MyViewModel = viewModel(factory = MyViewModel.Factory)
) {
    // ...
}

または、ViewModel ファクトリ DSL を使用し、より慣用的な Kotlin API を使用してファイルを作成します。

View

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory

class MyViewModel(
    private val myRepository: MyRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    // ViewModel logic

    // Define ViewModel factory in a companion object
    companion object {
        val Factory: ViewModelProvider.Factory = viewModelFactory {
            initializer {
                val savedStateHandle = createSavedStateHandle()
                val myRepository = (this[APPLICATION_KEY] as MyApplication).myRepository
                MyViewModel(
                    myRepository = myRepository,
                    savedStateHandle = savedStateHandle
                )
            }
        }
    }
}

2.5.0 より前のバージョンの ViewModel のファクトリ

2.5.0 より前のバージョンの ViewModel を使用している場合は、ViewModelProvider.Factory を拡張するクラスのサブセットのファクトリを提供し、create(Class<T>) 関数を実装する必要があります。ViewModel で必要となる依存関係に応じて、異なるクラスを拡張する必要があります。

Application または SavedStateHandle が不要な場合は、ViewModelProvider.Factory から拡張するだけです。

次の例では、リポジトリと SavedStateHandle タイプを依存関係として取得する ViewModel に対して AbstractSavedStateViewModelFactory を使用します。

View

class MyViewModel(
    private val myRepository: MyRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    // ViewModel logic ...

    // Define ViewModel factory in a companion object
    companion object {
        fun provideFactory(
            myRepository: MyRepository,
            owner: SavedStateRegistryOwner,
            defaultArgs: Bundle? = null,
        ): AbstractSavedStateViewModelFactory =
            object : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
                @Suppress("UNCHECKED_CAST")
                override fun <T : ViewModel> create(
                    key: String,
                    modelClass: Class<T>,
                    handle: SavedStateHandle
                ): T {
                    return MyViewModel(myRepository, handle) as T
                }
            }
    }
}

View

import androidx.annotation.NonNull;
import androidx.lifecycle.AbstractSavedStateViewModelFactory;
import androidx.lifecycle.SavedStateHandle;
import androidx.lifecycle.ViewModel;

public class MyViewModel extends ViewModel {
    public MyViewModel(
        MyRepository myRepository,
        SavedStateHandle savedStateHandle
    ) { /* Init ViewModel here */ }
}

public class MyViewModelFactory extends AbstractSavedStateViewModelFactory {

    private final MyRepository myRepository;

    public MyViewModelFactory(
        MyRepository myRepository
    ) {
        this.myRepository = myRepository;
    }

    @SuppressWarnings("unchecked")
    @NonNull
    @Override
    protected <T extends ViewModel> T create(
        @NonNull String key, @NonNull Class<T> modelClass, @NonNull SavedStateHandle handle
    ) {
        return (T) new MyViewModel(myRepository, handle);
    }
}

次に、ファクトリを使用して ViewModel を取得します。

View

import androidx.activity.viewModels

class MyActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels {
        MyViewModel.provideFactory((application as MyApplication).myRepository, this)
    }

    // Rest of Activity code
}

View

public class MyActivity extends AppCompatActivity {

    MyViewModel myViewModel = new ViewModelProvider(
        this,
        new MyViewModelFactory(((MyApplication) getApplication()).getMyRepository())
    ).get(MyViewModel.class);

    // Rest of Activity code
}

Compose

import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun MyScreen(
    modifier: Modifier = Modifier,
    viewModel: MyViewModel = viewModel(
        factory = MyViewModel.provideFactory(
            (LocalContext.current.applicationContext as MyApplication).myRepository,
            owner = LocalSavedStateRegistryOwner.current
        )
    )
) {
    // ...
}

ViewModel のライフサイクル

ViewModel オブジェクトのスコープは、ViewModel を取得する際に、ViewModelProvider に渡される ViewModelStoreOwnerLifecycle に設定されます。ViewModel は、スコープの ViewModelStoreOwner が完全に消えるまでメモリ内に残ります。

  • アクティビティの場合は終了時。
  • フラグメントの場合はデタッチ時。
  • Navigation エントリの場合はバックスタックからの削除時。

これにより、ViewModel は、構成の変更後に引き継ぐデータを保存するための優れたソリューションとなっています。

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

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

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

ViewModel API

ViewModelProvider.get() メソッドを使用すると、任意の ViewModelStoreOwner をスコープとする ViewModel のインスタンスを取得できます。Kotlin ユーザーには、最も一般的なユースケースで使用できるさまざまな拡張関数があります。すべての Kotlin 拡張関数の実装では、内部で ViewModelProvider API が使用されます。

最も近い ViewModelStoreOwner をスコープとする ViewModel

ViewModel をアクティビティ、フラグメント、またはナビゲーション グラフのデスティネーションにスコープ設定できます。アクティビティ、フラグメント、Navigation ライブラリによって提供される viewModels() 拡張関数と、Compose の viewModel() 関数を使用すると、最も近い ViewModelStoreOwner をスコープとする ViewModel のインスタンスを取得できます。

View

class MyActivity : AppCompatActivity() {

    // ViewModel API available in activity.activity-ktx
    // The ViewModel is scoped to `this` Activity
    val viewModel: MyViewModel by viewModels()
}

class MyFragment : Fragment() {

    // ViewModel API available in fragment.fragment-ktx
    // The ViewModel is scoped to `this` Fragment
    val viewModel: MyViewModel by viewModels()
}

View

public class MyActivity extends AppCompatActivity {

    // The ViewModel is scoped to `this` Activity
    MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
}

public class MyFragment extends Fragment {

    // The ViewModel is scoped to `this` Fragment
    MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
}

Compose

@Composable
fun MyScreen(
    modifier: Modifier = Modifier,
    // ViewModel API available in lifecycle.lifecycle-viewmodel-compose
    // The ViewModel is scoped to the closest ViewModelStoreOwner provided
    // via the LocalViewModelStoreOwner CompositionLocal. This could be the
    // host Activity or Fragment, or destination of the Navigation graph.
    viewModel: MyViewModel = viewModel()
) { /* ... */ }

任意の ViewModelStoreOwner をスコープとする ViewModel

View システムの ComponentActivity.viewModels() 関数と Fragment.viewModels() 関数、Compose の viewModel() 関数は、ViewModel のインスタンスがどの ViewModelStoreOwner にスコープ設定されるかを指定するオプションの ownerProducer パラメータを取ります。次のサンプルは、親フラグメントをスコープとする ViewModel のインスタンスを取得する方法を示しています。

View

class MyFragment : Fragment() {

    // ViewModel API available in fragment.fragment-ktx
    // The ViewModel is scoped to the parent of `this` Fragment
    val viewModel: SharedViewModel by viewModels(
        ownerProducer = { requireParentFragment() }
    )
}

View

public class MyFragment extends Fragment {

    SharedViewModel viewModel;

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        // The ViewModel is scoped to the parent of `this` Fragment
        viewModel = new ViewModelProvider(requireParentFragment())
            .get(SharedViewModel.class);
    }
}

Compose

@Composable
fun MyScreen(
    context: Context = LocalContext.current,
    // ViewModel API available in lifecycle.lifecycle-viewmodel-compose
    // The ViewModel is scoped to the parent of the host Fragment
    // where this composable function is called
    viewModel: SharedViewModel = viewModel(
        viewModelStoreOwner = (context as Fragment).requireParentFragment()
    )
) { /* ... */ }

フラグメントからアクティビティをスコープとする ViewModel を取得することは、アプリ内での一般的なユースケースであるため、activityViewModels() View 拡張関数を利用できます。View と Kotlin を使用しない場合は、上記と同じ API を使用して適切なオーナーを渡すことができます。

View

class MyFragment : Fragment() {

    // ViewModel API available in fragment.fragment-ktx
    // The ViewModel is scoped to the host Activity
    val viewModel: SharedViewModel by activityViewModels()
}

View

public class MyFragment extends Fragment {

    SharedViewModel viewModel;

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        // The ViewModel is scoped to the host Activity
        viewModel = new ViewModelProvider(requireActivity())
            .get(SharedViewModel.class);
    }
}

Compose

@Composable
fun MyScreen(
    context: Context = LocalContext.current,
    // ViewModel API available in lifecycle.lifecycle-viewmodel-compose
    // The ViewModel is scoped to the Activity of the host Fragment
    // where this composable function is called
    viewModel: SharedViewModel = viewModel(
        viewModelStoreOwner = (context as Fragment).requireActivity()
    )
) { /* ... */ }

ナビゲーション グラフをスコープとする ViewModel

ナビゲーション グラフも ViewModel のストアオーナーです。Navigation Fragment または Navigation Compose を使用している場合は、navGraphViewModels(graphId) View 拡張関数を使用してナビゲーション グラフをスコープとする ViewModel のインスタンスを取得できます。

View

class MyFragment : Fragment() {

    // ViewModel API available in navigation.navigation-fragment
    // The ViewModel is scoped to the `nav_graph` Navigation graph
    val viewModel: SharedViewModel by navGraphViewModels(R.id.nav_graph)

    // Equivalent navGraphViewModels code using the viewModels API
    val viewModel: SharedViewModel by viewModels(
        { findNavController().getBackStackEntry(R.id.nav_graph) }
    )
}

View

public class MyFragment extends Fragment {

    SharedViewModel viewModel;

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        NavController navController = NavHostFragment.findNavController(this);
        NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph);

        // The ViewModel is scoped to the `nav_graph` Navigation graph
        viewModel = new ViewModelProvider(backStackEntry).get(SharedViewModel.class);
    }
}

Compose

@Composable
fun MyAppNavHost() {
    // ...
    composable("myScreen") { backStackEntry ->
        val parentEntry = remember(backStackEntry) {
            navController.getBackStackEntry("parentNavigationRoute")
        }
        // The ViewModel is scoped to the `parentNavigationRoute` Navigation graph
        val parentViewModel: SharedViewModel = viewModel(parentEntry)
        // ...
    }
}

Jetpack Navigation に加えて Hilt を使用している場合は、次のように hiltNavGraphViewModels(graphId) API を使用できます。

View

class MyFragment : Fragment() {

    // ViewModel API available in hilt.hilt-navigation-fragment
    // The ViewModel is scoped to the `nav_graph` Navigation graph
    // and is provided using the Hilt-generated ViewModel factory
    val viewModel: SharedViewModel by hiltNavGraphViewModels(R.id.nav_graph)
}

View

public class MyFragment extends Fragment {

    SharedViewModel viewModel;

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        NavController navController = NavHostFragment.findNavController(this);
        NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph);

        // The ViewModel is scoped to the `nav_graph` Navigation graph
        // and is provided using the Hilt-generated ViewModel factory
        viewModel = new ViewModelProvider(
            backStackEntry,
            HiltViewModelFactory.create(getContext(), backStackEntry)
        ).get(SharedViewModel.class);
    }
}

Compose

@Composable
fun MyAppNavHost() {
    // ...
    composable("myScreen") { backStackEntry ->
        val parentEntry = remember(backStackEntry) {
            navController.getBackStackEntry("parentNavigationRoute")
        }

        // ViewModel API available in hilt.hilt-navigation-compose
        // The ViewModel is scoped to the `parentNavigationRoute` Navigation graph
        // and is provided using the Hilt-generated ViewModel factory
        val parentViewModel: SharedViewModel = hiltViewModel(parentEntry)
        // ...
    }
}

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

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

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

View

class SharedViewModel : ViewModel() {
    val selected = MutableLiveData()

    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 ->
            // Update the UI
        })
    }
}

View

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

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

    public LiveData 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 を更新します。

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

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

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

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

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

追加情報

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

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

参考情報

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

サンプル

Codelab

ブログ

動画