Navigation 3
このページの内容はすべて、任意のアーティファクト navigation にあります。パッケージは jp.kaleidot725.pulse.navigation です。
仕組みの解説は Navigation 3 ガイド を、mvi だけで使う場合は ViewModel を参照してください。
rememberPulseViewModel
@Composable
inline fun <reified ViewModel : PulseViewModel<*, *, *, *, *>> rememberPulseViewModel(
key: String? = null,
noinline factory: () -> ViewModel,
): ViewModel現在の ViewModelStoreOwner の ViewModelStore に保持される ViewModel を生成します。コンポジションが再起動しても作り直されません。同じインスタンスが再利用されます。
パラメータ
| パラメータ | 型 | 説明 |
|---|---|---|
key | String? | オーナー内で一意なキー。既定値は ViewModel の完全修飾クラス名 |
factory | () -> ViewModel | オーナーごとに一度だけ呼ばれ、ViewModel を生成する |
ライフサイクルの挙動
- ViewModel は最初のコンポジションで生成され、同じオーナー配下の以降のコンポジションでは再利用される
onSetup()は、PulseContentが ViewModel を最初に観測したときに一度だけ実行される- コンポジションを作り直しても
onSetup()は繰り返されない - オーナーの
ViewModelStoreが破棄されると ViewModel のスコープはキャンセルされる - 状態はメモリ上にのみ保持され、プロセス終了後には復元されない
オーナーの解決
内部では LocalViewModelStoreOwner.current を読みます。そのオーナーの ViewModelStore に、key でインスタンスを保持します。したがって、呼び出し箇所でスコープにあるオーナーが ViewModel の寿命を決めます。オーナーを変えるものはすべて、ViewModel の寿命も変えます。
| スコープにあるオーナー | ViewModel の寿命 |
|---|---|
Compose Desktop の Window | 画面が生きている間。コンポジションの再起動をまたいで生き残る |
Navigation 3 のエントリ(NavDisplay の entryDecorators に rememberPulseNavEntryDecorators() を渡した場合) | ルートがバックスタックにある間。ルートが pop されるとキャンセルされる |
CompositionLocalProvider(LocalViewModelStoreOwner provides ...) で自分で与えたもの | そのオーナーを保持している間 |
| ホストが何も提供しない | rememberPulseViewModel はメッセージ付きで失敗する。寿命を所有するものが無いので、オーナーを与えるか mvi だけで使う |
計画しておくべき 2 つの帰結
keyはグローバルではなくオーナー単位で一意です。 同じオーナーの下に同じ型の ViewModel を 2 つ置くと衝突します。既定のキーはクラス名です。明示的なキーを与えるか、別のオーナーの下に置いてください- オーナーが破棄されるまで、ViewModel がオーナーから取り除かれることはありません。 長生きするオーナーの下で ViewModel を生成し続けると、オーナーの寿命の間ずっと溜まっていきます。画面が二度と使わない ViewModel を作るなら、より狭いオーナーにスコープしてください
TIP
テストでは、独自のオーナーを与えることで両方の側面を確認できます。オーナーをコンポジションの作り直しをまたいで保持すれば、コンポジションの再起動を再現できます。オーナーを破棄すれば、画面が消える状況を再現できます。
例
val viewModel = rememberPulseViewModel { CounterViewModel(CounterRepository()) }
// 同じ ViewModel 型のインスタンスを 2 つ置くには別々のキーが必要
val left = rememberPulseViewModel(key = "left") { CounterViewModel(leftRepository) }
val right = rememberPulseViewModel(key = "right") { CounterViewModel(rightRepository) }
// 画面全体ではなく Navigation 3 の destination にスコープする
NavDisplay(
backStack = backStack,
entryDecorators = rememberPulseNavEntryDecorators(),
entryProvider = entryProvider {
entry<Route.Counter> {
val viewModel = rememberPulseViewModel { CounterViewModel(CounterRepository()) }
// ...
}
},
)rememberPulseContainer
@Composable
inline fun <reified Container : PulseContainer<*, *>> rememberPulseContainer(
key: String? = null,
noinline factory: () -> Container,
): Containerコンポジションの再起動をまたいで生き残る Container を生成します。Unicast の購読は維持されます。オーナーの ViewModelStore が破棄されるときに、PulseContainer.close() が呼ばれます。
パラメータ
| パラメータ | 型 | 説明 |
|---|---|---|
key | String? | オーナー内で一意なキー。既定値は Container の完全修飾クラス名 |
factory | () -> Container | オーナーごとに一度だけ呼ばれ、Container を生成する |
例
val viewModel = rememberPulseViewModel { CounterViewModel(CounterRepository()) }
val container = rememberPulseContainer { CounterContainer(viewModels = listOf(viewModel)) }rememberPulseNavEntryDecorators
@Composable
fun <T : Any> rememberPulseNavEntryDecorators(): List<NavEntryDecorator<T>>NavDisplay に渡す NavEntryDecorator のリストです。ViewModel をバックスタックエントリにスコープするために必要です。saveable state holder のデコレータと、ViewModel のデコレータの 2 つを含みます。
NavDisplay の entryDecorators の既定値は saveable state holder のみです。ViewModel のデコレータだけを渡すと、saveable state が黙って失われます。この関数は両方を返します。
NavDisplay(
backStack = backStack,
entryDecorators = rememberPulseNavEntryDecorators(),
entryProvider = entryProvider {
entry<Route.Counter> {
// このエントリにスコープされる。ルートが pop されるとキャンセルされる
val viewModel = rememberPulseViewModel { CounterViewModel(CounterRepository()) }
// ...
}
},
)