Composeの画面遷移で、前の画面に結果を返す方法についてまとめておきます。
基本的にはFragmentと同じような感じで NavBackStackEntry
の SavedStateHandle
を使用していきます。
結果を返す
ScreenAからScreenBに遷移して、ScreenBでの画面の結果をScreenAに返す処理です。
navController.previousBackStackEntry?.run {
savedStateHandle["result"] = "Foo"
}
上記のように NavHostController#previousBackStackEntry
の SavedStateHandle
にKey-Valueで設定するだけです。
previousBackStackEntry
っていうのが大事で前の画面の NavBackStackEntry
アクセスしています。
結果を受け取る
今度はScreenA側でScreenBの結果を受け取る方法です。
val result: String? = navController
.currentBackStackEntry?.savedStateHandle?.get("result")
受け取るには NavHostController#currentBackStackEntry
の SavedStateHandle
から取得することが出来ます。
受け取るときは currentBackStackEntry
を使います。
結果の削除
結果を受け取るときの注意としては、基本的にセットした結果は自分で削除しない限りは残り続けます。画面回転等でも残ります。
そのため、結果がセットされた後にRecompositionが行われると何度も結果を取得することが出来ます。
UI状態して同じ結果が返りつづけるのを期待している場合は問題ないかもですが、もし、結果によって一度だけ処理したい場合は対応が必要になります。例えば、結果を受け取ったら画面をリフレッシュするとかの場合です。
ワンショットの処理が行いたい場合は以下のように処理後に remove
で削除することで何度も呼び出されることはなくなります。
navController.currentBackStackEntry?.run {
if (savedStateHandle.contains("result")) { // 結果があるか確認
// 結果取得
val result: String? = savedStateHandle["result"]
// 結果を使った処理
viewModel.someAction(result)
// 結果を削除
savedStateHandle.remove<String>("result")
}
}
また、 remove
は削除前にセットされてる値を返すので、以下のような書き方もで結果の取得と同時に削除することもできます。
val result: String? = navController
.currentBackStackEntry?.savedStateHandle?.remove("result")
ただ、個人的には確実に処理後に結果を削除したほうが良いと思うので、前者の実装のほうが良いかなと思います。
Recompositionへの対応
前述までの方法でも問題ないのですが、ComposeはRecompositionにより何度も処理が実行される可能性があります。
そのため、これまでの実装だとRecompositionごとに画面の結果があるかを確認することになります。
LaunchedEffect
等で一度だけ実行されるようにしておくと良いと思います。
LaunchedEffect(Unit) {
navController.currentBackStackEntry?.run {
if (savedStateHandle.contains("result")) {
// ...
}
}
}