Navigation Composeめも
saveState = true を設定すると何が起きるか?
popUpTo(id) {
saveState = true
}
PopUpToBuilderのsaveStateがtrueになり、
それをNavOptionBuilderのsaveStateをtrueにする
// NavOptionsBuilder
public fun popUpTo(@IdRes id: Int, popUpToBuilder: PopUpToBuilder.() -> Unit = {}) {
val builder = PopUpToBuilder().apply(popUpToBuilder)
saveState = builder.saveState
}
saveStateがtrueのnavOptionsを渡すと
navController.navigate(route, navOptions)
辿っていくと以下のシグネチャのnavigate関数までたどり着く(navigateという同名関数が多い)
@MainThread
private fun navigate(
node: NavDestination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
) {}
読み進めると以下でpopしてるっぽい
popped = popBackStackInternal(
navOptions.popUpToId,
navOptions.isPopUpToInclusive(),
navOptions.shouldPopUpToSaveState()
)
NavOption.popUpToSaveStateはprivateでshouldPopUpToSaveState()で公開されているが、
shouldPopUpToSaveState()
はここでしか使用されていない。
つまり、popBackStackInternal
を確認すれば何をしてるか分かりそう
popUpToで指定したIDまでのNavigatorをpopOperations
に格納します。
指定したidが見つかった時点でループを抜けます。
// popBackStackInternal
val popOperations = mutableListOf<Navigator<*>>()
val iterator = backQueue.reversed().iterator()
var foundDestination: NavDestination? = null
while (iterator.hasNext()) {
val destination = iterator.next().destination
val navigator = _navigatorProvider.getNavigator<Navigator<*>>(
destination.navigatorName
)
if (inclusive || destination.id != destinationId) {
popOperations.add(navigator)
}
if (destination.id == destinationId) {
foundDestination = destination
break
}
}
Navigatorのセットは
NavControllerが作られた時と
init {
_navigatorProvider.addNavigator(NavGraphNavigator(_navigatorProvider))
_navigatorProvider.addNavigator(ActivityNavigator(context))
}
rememberから作られた時はこちらも追加されている模様
private fun createNavController(context: Context) =
NavHostController(context).apply {
navigatorProvider.addNavigator(ComposeNavigator())
navigatorProvider.addNavigator(DialogNavigator())
}
NavControllerは一つのNavigatorProviderを持っていて、
NavigatorProviderはNavigatorを
Map<String, Navigator>
で管理している。
navController.navigatorProvider.navigators
から実際に取得できるの見てみると実際に上でセットした値が入っている
navigation -> androidx.navigation.NavGraphNavigator
activity -> androidx.navigation.ActivityNavigator
composable -> androidx.navigation.compose.ComposeNavigator
dialog -> androidx.navigation.compose.DialogNavigator
keyはNavigatorProvider.getNameForNavigator
からアノテーションの名前を取っているっぽい
つまり先ほどのpopOperations
の中には、今回はComposeなので
ComposeNavigator<それぞれのDestination>のデータが入っている。
NavController.popBackStackInternalの続きをみると、
navigator.popBackStackInternal() (NavController
)
↓
ComposeNavigator.popBackStack()
↓
NavigationState.popWithTranstion()
↓
NavControllerNavigatorState.popWithTransition()
ここの処理を見てみると以下のような処理がある。
つまりpopしたNavBackStackEntryをkeyにsaveStateの値を保存している。
この値を後で復元の時に使用していると思われる
// NavController
private val entrySavedState = mutableMapOf<NavBackStackEntry, Boolean>()
override fun popWithTransition(popUpTo: NavBackStackEntry, saveState: Boolean) {
super.popWithTransition(popUpTo, saveState)
entrySavedState[popUpTo] = saveState
}
NavigatoStateのbackStackを削除した後、そのBackStackEntryをもらい
実際にNavControllerが保持するbackQueueから削除する処理をpopEntryFromBackStack
で行う。
navigator.popBackStackInternal(backQueue.last(), saveState) { entry ->
receivedPop = true
popped = true
popEntryFromBackStack(entry, saveState, savedState)
}
popEntryFromBackStackの大事そうな処理の場所を見ると
saveState
がtrueの場合
- savedState.addFirst(NavBackStackEntryState(entry))を呼ぶ
- viewModel?.clear(entry.id)を終了させる
1については次に進む
2はViewModelにonCleared
にログを入れれ、saveState=falseで動作確認すると、確かにpopの時にViewModelが破棄されている事がわかる。
事がわかります。
if (entry.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
if (saveState) {
// Move the state through STOPPED
entry.maxLifecycle = Lifecycle.State.CREATED
// Then save the state of the NavBackStackEntry
savedState.addFirst(NavBackStackEntryState(entry))
}
if (!transitioning) {
entry.maxLifecycle = Lifecycle.State.DESTROYED
unlinkChildFromParent(entry)
} else {
entry.maxLifecycle = Lifecycle.State.CREATED
}
}
if (!saveState && !transitioning) {
viewModel?.clear(entry.id)
}