
Jetpack Compose の Navigation3 では、画面遷移の引数として RouteB(val id: String) のようなデータクラスを渡します。
このとき、同じ RouteB でも id が違えば「別の画面(別の ViewModel)」として扱いたいですよね。
ここで重要になるのが EntryDecorator と viewModel(key = ...) の関係です。
🤔 結論:Decorator が「鍵」を管理してくれるか否か
一言でいうと、こうなります。
Decorator を使わない場合:
手動で viewModel(key = "unique_id") を指定する必要がある。
Decorator を使えば:
viewModel() の引数は不要。
Decorator が自動で各エントリに個別の ViewModelStore を割り当ててくれる。
🤔 1. Decorator を使わないパターン(手動管理)
Navigation3 の基本機能 NavDisplay を使う場合、ViewModel の生存期間はデフォルトの「Activity 全体」に紐づきます。
同じ RouteB でも id ごとに ViewModel を作り分けたい場合、以下のように手動で key を渡して、内部のキャッシュを分ける必要があります。
entry<RouteB> { key ->
// key(RouteBのインスタンス)の id を使って、ViewModelを区別する
val vm = viewModel(
key = key.id, // ← これが必要!
factory = RouteBViewModel.Factory(key)
)
ScreenB(vm)
}
これを忘れると、id = "1" の画面から id = "2" の画面へ遷移しても、同じ ViewModel インスタンスが使い回されてしまい、表示内容が更新されない というバグに繋がります。
🤔 2. Decorator を使うパターン(Navigation3 の推奨)
サンプルの BasicViewModelsActivity で採用されている方法です。
NavDisplay の設定に rememberViewModelStoreNavEntryDecorator() を追加します。
NavDisplay(
backStack = backStack,
entryDecorators = listOf(
rememberSaveableStateHolderNavEntryDecorator(),
rememberViewModelStoreNavEntryDecorator() // ← これが魔法のスパイス
),
entryProvider = entryProvider {
entry<RouteB> { key ->
// key 指定が不要になる!
val vm = viewModel(factory = RouteBViewModel.Factory(key))
ScreenB(vm)
}
}
)
なぜ key が不要になるのか?
ViewModelStoreNavEntryDecorator は、バックスタックにある 「各エントリ(NavEntry)」ごとに独立した ViewModelStore を生成してくれます。
RouteB("1") のエントリ ➔ 専用の ViewModelStore A が用意される
RouteB("2") のエントリ ➔ 専用の ViewModelStore B が用意される
viewModel() 関数は、その時点の LocalViewModelStoreOwner(Decorator が提供するエントリごとのストア)を参照します。そのため、わざわざ key を指定しなくても、エントリが違えば自動的に別の ViewModel が作られる仕組みです。
🤔 まとめ:どっちを使うべき?
基本的には「Decorator を使う」のが正解です。
理由1:
コードがシンプルになる(key の指定漏れがなくなる)。
理由2:
画面を戻ったときに ViewModel が正しく破棄されるなど、ライフサイクル管理が Navigation3 のエントリと完全に同期する。
「この画面だけは特殊な管理をしたい」という場合を除き、rememberViewModelStoreNavEntryDecorator() をセットアップして、型安全な key をそのまま Factory に渡すスタイルを基本にしましょう。





