ViewModel スコープ用 API Android Jetpack の一部。
スコープは、ViewModel を効果的に使用するうえで重要です。各 ViewModel は、ViewModelStoreOwner インターフェースを実装するオブジェクトをスコープとしています。ViewModel のスコープを簡単に管理できる API がいくつかあります。このドキュメントでは、知っておくべき主要な手法について説明します。
ViewModelProvider.get() メソッドを使用すると、任意の ViewModelStoreOwner をスコープとする ViewModel のインスタンスを取得できます。Kotlin ユーザーには、最も一般的なユースケースで使用できるさまざまな拡張関数があります。すべての Kotlin 拡張関数の実装では、内部で ViewModelProvider API が使用されます。
最も近い ViewModelStoreOwner をスコープとする ViewModel
ViewModel をコンポーザブル、アクティビティ、またはナビゲーション グラフのデスティネーションにスコープ設定できます。Compose の viewModel() 関数を使用すると、最も近い ViewModelStoreOwner をスコープとする ViewModel のインスタンスを取得できます。
import androidx.lifecycle.viewmodel.compose.viewModel @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. In order of proximity, // this could be the destination of a Navigation graph // or the host Activity. viewModel: MyViewModel = viewModel() ) { /* ... */ }
任意の ViewModelStoreOwner をスコープとする ViewModel
viewModel() 関数は、ViewModel のインスタンスがどの ViewModelStoreOwner にスコープ設定されるかを指定するオプションの viewModelStoreOwner パラメータを取ります。
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.ViewModelStoreOwner @Composable fun MyScreen( // A custom owner passed in, such as a parent NavBackStackEntry customOwner: ViewModelStoreOwner, // The ViewModel is now scoped to the provided customOwner viewModel: MyViewModel = viewModel(viewModelStoreOwner = customOwner) ) { /* ... */ }
コンポーザブルをスコープとする ViewModel
rememberViewModelStoreOwner() を使用すると、ViewModel をコンポーザブルの呼び出しサイトに直接スコープできます。これは、ページや遅延リストのアイテムなど、状態に基づいて画面に動的に追加または削除される UI コンポーネントに特に役立ちます。ViewModelStoreOwner を所有するコンポーザブルがコンポジションを離れると、関連付けられた ViewModelStore がクリアされ、ViewModel が破棄されます。
rememberViewModelStoreOwner() を使用して、構成の変更に影響されないライフサイクル対応ストアを作成します。
@Composable
fun RememberViewModelStoreOwnerSample() {
// Create a ViewModelStoreOwner scoped to this specific call site.
// When this composable leaves the composition,
// the associated ViewModelStore will be cleared.
val scopedOwner = rememberViewModelStoreOwner()
CompositionLocalProvider(LocalViewModelStoreOwner provides scopedOwner) {
// This ViewModel is scoped to `scopedOwner`.
// It will survive configuration changes but will be cleared when
// the composable is removed from the UI tree.
val viewModel = viewModel { TestViewModel("scoped_data") }
// Use the ViewModel
}
}
HorizontalPager や複数の独立したスコープを必要とするケースなど、より複雑な実装の場合は、rememberViewModelStoreProvider() を使用します。これにより、異なるキー(ページ インデックスなど)に対して個別の ViewModelStoreOwner インスタンスを生成できます。これにより、各ページは独自の独立した ViewModel 状態を維持します。
@Composable
fun RememberViewModelStoreProviderSample() {
val storeProvider = rememberViewModelStoreProvider()
val pages = listOf("Page 1", "Page 2", "Page 3")
HorizontalPager(pageCount = pages.size) { page ->
// Create a ViewModelStoreOwner for the specific page using the provider.
val pageOwner = rememberViewModelStoreOwner(provider = storeProvider, key = page)
CompositionLocalProvider(LocalViewModelStoreOwner provides pageOwner) {
val pageViewModel = viewModel { TestViewModel(pages[page]) }
// Use pageViewModel
}
}
}
ナビゲーション グラフをスコープとする ViewModel
ナビゲーション グラフも ViewModel のストアオーナーです。Navigation Compose を使用している場合は、getBackStackEntry() 関数を使用してナビゲーション グラフをスコープとする ViewModel のインスタンスを取得できます。
viewModel() は、LocalViewModelStoreOwner CompositionLocal によって提供される最も近い ViewModelStoreOwner からインスタンスを取得します。Jetpack Navigation を使用する一般的な Compose アプリケーションでは、このオーナーは現在の Navigation バックスタック エントリです。つまり、そのデスティネーションがバックスタックに存在する限り、ViewModel はメモリ内に残ります。
import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun MyAppNavHost() { // ... composable("myScreen") { backStackEntry -> // Retrieve the NavBackStackEntry of "parentNavigationRoute" val parentEntry = remember(backStackEntry) { navController.getBackStackEntry("parentNavigationRoute") } // Get the ViewModel scoped to the `parentNavigationRoute` Nav graph val parentViewModel: SharedViewModel = viewModel(parentEntry) // ... } }
Jetpack Navigation に加えて Hilt を使用している場合は、次のように hiltNavGraphViewModels(graphId) API を使用できます。
import androidx.hilt.navigation.compose.hiltViewModel @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) // ... } }
参考情報
ViewModel とスコープの詳細については、以下の参考情報をご覧ください。