初めに
JetpackCompose
でviewModel()
を呼び出してViewModel
を取得する場合、CompositionLocal
経由で取得したViewModelStoreOwner
がViewModel
のスコープになります。標準でViewModelStoreOwner
を実装しているクラスにはComponentActivity
、Fragment
、NavBackStackEntry
があり、呼び出し階層で一番近い物が適用されます。現在のAndroidアプリの作りでは、NavBackStackEntry
をスコープとしたViewModel
を取得して使用することが多いと思います。また、viewModel()
の引数でViewModelStoreOwner
を指定することで、特定のスコープのViewModel
を取得することもできます。Hilt
を使ってDIする場合でもviewModel()
をhiltViewModel()
に置き換えるだけで使い方は変わりません。
ComponentActivity、Fragment
ナビゲーショングラフのデスティネーション内から、ComponentActivity
やFragment
をスコープとしたViewModel
を取得する方法です。ComponentActivity
やFragment
といった大きなスコープのViewModel
は、アプリ全体を通して共有したい値を保持する場合などに使用されます。
@Composable
fun MainContent(
modifier: Modifier = Modifier
) {
val navController = rememberNavController()
NavHost(
modifier = modifier,
navController = navController,
startDestination = MenuPageRoute
) {
composable<MenuPageRoute> {
// Fragmentを使用しておらず、ActivityスコープのViewModelを取得する場合
val viewModel = viewModel<MainActivityViewModel>(
LocalContext.current as ComponentActivity)
// Fragmentを使用しており、ActivityスコープのViewModelを取得する場合
val viewModel = viewModel<MainActivityViewModel>(
(LocalContext.current as Fragment).requireActivity())
// Fragmentを使用しており、FragmentスコープのViewModelを取得する場合
val viewModel = viewModel<MainActivityViewModel>(
LocalContext.current as Fragment)
MenuPage(
navController
)
}
}
}
異なるナビゲーショングラフのデスティネーション
ナビゲーショングラフのデスティネーション内から、異なるデスティネーションをスコープとしたViewModelを取得する方法です。ユーザー登録画面など、複数のデスティネーション間で共通のViewModelを参照したい場合などに使用されます。
@Composable
fun MainContent(
modifier: Modifier = Modifier
) {
val navController = rememberNavController()
NavHost(
modifier = modifier,
navController = navController,
startDestination = MenuPageRoute
) {
composable<MenuPageRoute> {
val viewModel = viewModel<MainActivityViewModel>(
MenuPage(
navController
)
}
+ composable<ScreenARoute> { backStackEntry ->
+ // ScreenARouteスコープのViewModelを取得
+ val viewModel = viewModel<ScreenPageViewModel>()
+ ScreenAPage(
+ navController
+ )
+ }
+ composable<ScreenBRoute>{ backStackEntry ->
+ // ScreenARouteのBackStackEntryを取得
+ val parentBackStackEntry = remember(backStackEntry) {
+ navController.getBackStackEntry(ScreenARoute)
+ }
+ // ScreenARouteスコープのViewModelを取得
+ val viewModel = viewModel<ScreenPageViewModel>(parentBackStackEntry)
+ ScreenBPage(
+ navController
+ )
+ }
}
}
公式サイトには上記の様に、異なるデスティネーションのroute
を直接指定してBackStackEntry
を取得する例が記載されていますが、ScreenARoute
を呼び出す前にScreenARoute
を呼び出すとエラーが発生するため、下記の様に、ナビゲーショングラフをネストさせて利用した方が安全です。下記の例では、ScreenCRoute
、ScreenBRoute
、どちらかのBackStackEntry
が存在していれば、ScreenParentRoute
のBackStackEntry
も存在するため、共通のViewModel
も存在することになります。
@Composable
fun MainContent(
modifier: Modifier = Modifier
) {
val navController = rememberNavController()
NavHost(
modifier = modifier,
navController = navController,
startDestination = MenuPageRoute
) {
composable<MenuPageRoute> {
val viewModel = viewModel<MainActivityViewModel>(
MenuPage(
navController
)
}
+ navigation<ScreenParentRoute>(
+ startDestination = ScreenCRoute,
+ ) {
+ composable<ScreenCRoute> { backStackEntry ->
+ // ScreenParentRouteのBackStackEntryを取得
+ val parentBackStackEntry = remember(backStackEntry) {
+ navController.getBackStackEntry(ScreenParentRoute)
+ }
+ // ScreenParentRouteスコープのViewModelを取得
+ val viewModel = viewModel<ScreenPageViewModel>(parentBackStackEntry)
+ ScreenCPage(
+ navController
+ )
+ }
+ composable<ScreenDRoute>{ backStackEntry ->
+ // ScreenParentRouteのBackStackEntryを取得
+ val parentBackStackEntry = remember(backStackEntry) {
+ navController.getBackStackEntry(ScreenParentRoute)
+ }
+ // ScreenParentRouteスコープのViewModelを取得
+ val viewModel = viewModel<ScreenPageViewModel>(parentBackStackEntry)
+ ScreenDPage(
+ navController
+ )
+ }
+ }
}
}