2024. 3. 12. 00:09ㆍAndroid/개발 CASE
BottomNavigationItem(
icon = {
Icon(
painter = painterResource(id = screen.drawableResId),
contentDescription = stringResource(screen.stringResId)
)
},
label = { Text(stringResource(screen.stringResId)) },
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
onClick = {
navController.navigate(screen.route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
1. navConroller.navigate(screen.route){ ... }
// NavController
public fun navigate(route: String, builder: NavOptionsBuilder.() -> Unit) {
navigate(route, navOptions(builder))
}
1-1. navOptions(builder) : builder라는 람다를 실행한 결과인 NavOptions 인스턴스 생성
// NavOptionsBuilder
public fun navOptions(optionsBuilder: NavOptionsBuilder.() -> Unit): NavOptions =
NavOptionsBuilder() // NavOptionsBuilder를 생성
.apply(optionsBuilder) // param으로 넘겨준 람다를 실행해 NavOptionsBuilder에서 설정값 변경
.build() // NavOptionsBuilder의 build 필드(NavOptions.Builder())를 이용해
// build.apply{ ... }.build()로 설정값 변경해 NavOptions 생성
* NavOptionsBuilder : DSL for constructing a new NavOptions
* NavOptions.Builder : Builder for constructing new instances of NavOptions
2. navigate(route, navOptions(builder))
// NavController
@JvmOverloads
public fun navigate(
route: String,
navOptions: NavOptions? = null,
navigatorExtras: Navigator.Extras? = null
) {
navigate(
// Builder 동반객체의 fromUri()를 통해 uri가 설정된 Builder 생성
NavDeepLinkRequest.Builder.fromUri(createRoute(route).toUri())
.build(), // NavDeepLinkRequest 생성
navOptions,
navigatorExtras
)
}
2-1. NavDeepLinkRequest.Builder.fromUri(createRoute(route).toUri()).build() : route를 Uri로 변경후 해당 값 세팅된 NavDeepLinkRequest 생성
public class Builder private constructor() {
private var uri: Uri? = null
private var action: String? = null
private var mimeType: String? = null
public fun build(): NavDeepLinkRequest {
return NavDeepLinkRequest(uri, action, mimeType)
}
...
public companion object {
/**
* Creates a [NavDeepLinkRequest.Builder] with a set uri.
*
* @param uri The uri to add to the NavDeepLinkRequest
* @return a [Builder] instance
*/
@JvmStatic
public fun fromUri(uri: Uri): Builder {
val builder = Builder()
builder.setUri(uri)
return builder
}
}
}
* NavDeepLinkRequest : A request for a deep link in a NavDestination.
NavDeepLinkRequest are used to check if a NavDeepLink exists for a NavDestination and to navigate to a NavDestination with a matching NavDeepLink.
* NavDestination : NavDestination represents one node within an overall navigation graph.
Each destination is associated with a Navigator which knows how to navigate to this particular destination.
NavDestinations should be created via Navigator.createDestination.
3. navigate(NavDeepLinkRequest.Builder.fromUri(createRoute(route).toUri()).build(), navOptions, navigatorExtras)
// NavController
@MainThread
public open fun navigate(
request: NavDeepLinkRequest,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
) {
val deepLinkMatch = _graph!! // Backing Property로, NavGraph를 의미
.matchDeepLink(request)
// 현재 NavGraph에 직접적으로 연결된Deep Link를 통해 탐색한 최적값과 Deep Link와 매치되는
// 모든 자식 destination을 통해 탐색한 최적값 중 최적의 값인 DeepLinkMatch 반환
if (deepLinkMatch != null) {
val destination = deepLinkMatch.destination
val args = destination.addInDefaultArgs(deepLinkMatch.matchingArgs) ?: Bundle()
val node = deepLinkMatch.destination
val intent = Intent().apply {
setDataAndType(request.uri, request.mimeType)
action = request.action
}
args.putParcelable(KEY_DEEP_LINK_INTENT, intent) // uri가 mimeType으로 지정된 intent를
// bundle에 담아줌
navigate(node, args, navOptions, navigatorExtras)
} else {
throw IllegalArgumentException(
"Navigation destination that matches request $request cannot be found in the " +
"navigation graph $_graph"
)
}
}
* NavGraph : NavGraph is a collection of NavDestination nodes fetchable by ID.
A NavGraph serves as a 'virtual' destination: while the NavGraph itself will not appear on the back stack, navigating to the NavGraph will cause the starting destination to be added to the back stack.
Construct a new NavGraph. This NavGraph is not valid until you add a destination and set the starting destination.
* NavDeepLink : NavDeepLink encapsulates the parsing and matching of a navigation deep link.
4. navigate(node, args, navOptions, navigatorExtras) : NavDestination을 해당 Navigator에서 관리하는 back stack에서 pop하고,
// NavController
@MainThread
private fun navigate(
node: NavDestination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
) {
navigatorState.values.forEach { state ->
state.isNavigating = true
}
var popped = false
var launchSingleTop = false
var navigated = false
if (navOptions != null) {
if (navOptions.popUpToId != -1) {
popped = popBackStackInternal(
navOptions.popUpToId,
navOptions.isPopUpToInclusive(),
navOptions.shouldPopUpToSaveState()
)
}
}
val finalArgs = node.addInDefaultArgs(args)
// Now determine what new destinations we need to add to the back stack
if (navOptions?.shouldRestoreState() == true && backStackMap.containsKey(node.id)) {
navigated = restoreStateInternal(node.id, finalArgs, navOptions, navigatorExtras)
} else {
val currentBackStackEntry = currentBackStackEntry
val navigator = _navigatorProvider.getNavigator<Navigator<NavDestination>>(
node.navigatorName
)
if (navOptions?.shouldLaunchSingleTop() == true &&
node.id == currentBackStackEntry?.destination?.id
) {
unlinkChildFromParent(backQueue.removeLast())
val newEntry = NavBackStackEntry(currentBackStackEntry, finalArgs)
backQueue.addLast(newEntry)
val parent = newEntry.destination.parent
if (parent != null) {
linkChildToParent(newEntry, getBackStackEntry(parent.id))
}
navigator.onLaunchSingleTop(newEntry)
launchSingleTop = true
} else {
// Not a single top operation, so we're looking to add the node to the back stack
val backStackEntry = NavBackStackEntry.create(
context, node, finalArgs, hostLifecycleState, viewModel
)
navigator.navigateInternal(listOf(backStackEntry), navOptions, navigatorExtras) {
navigated = true
addEntryToBackStack(node, finalArgs, it)
}
}
}
updateOnBackPressedCallbackEnabled()
navigatorState.values.forEach { state ->
state.isNavigating = false
}
if (popped || navigated || launchSingleTop) {
dispatchOnDestinationChanged()
} else {
updateBackStackLifecycle()
}
}
4-1. popBackStackInternal(
navOptions.popUpToId,
navOptions.isPopUpToInclusive(),
navOptions.shouldPopUpToSaveState()
) : NavOptions에 지정된 방식으로 사용하고 있는 Navigator를 찾아 해당 back stack에서 주어진 NavDestination을 pop하고, inclusive, saveState에 따라 저장된 값이 있으면 BackStackMap에 NavDestination ID를 저장해 NavController에서 관리하고 있는 backQueue에서 찾을 수 있게 처리
// NavController
private fun popBackStackInternal(
@IdRes destinationId: Int,
inclusive: Boolean,
saveState: Boolean = false
): Boolean {
if (backQueue.isEmpty()) {
// Nothing to pop if the back stack is empty
return false
}
val popOperations = mutableListOf<Navigator<*>>()
val iterator = backQueue // ArrayDeque<NavBackStackEntry>
.reversed().iterator() // 뒤집힌 형태로 반복자를 획득
// (popOperations에 순차적으로 넣기 위해)
var foundDestination: NavDestination? = null
while (iterator.hasNext()) {
val destination = iterator.next().destination // NavBackStackEntry 내의 NavDestination
val navigator = _navigatorProvider // NavController 생성 시 등록된 Navigator 목록
.getNavigator<Navigator<*>>(
destination.navigatorName
) // destination과 관련된 navigator를 이름으로 탐색해서 획득
if (inclusive // 내부 back stack에서 destination도 pop up 되어야하는지 여부
|| destination.id != destinationId) { // 각 destination마다 부여되는 고유식별자
popOperations // Navigator를 담을 수 있는 MutableList
.add(navigator) // list에 해당 Navigator 추가
}
if (destination.id == destinationId) {
foundDestination = destination
break
}
}
if (foundDestination == null) {
// We were passed a destinationId that doesn't exist on our back stack.
// Better to ignore the popBackStack than accidentally popping the entire stack
val destinationName = NavDestination.getDisplayName(
context, destinationId
)
Log.i(
TAG,
"Ignoring popBackStack to destination $destinationName as it was not found " +
"on the current back stack"
)
return false
}
var popped = false
val savedState = ArrayDeque<NavBackStackEntryState>()
for (navigator in popOperations) {
var receivedPop = false
navigator.popBackStackInternal(backQueue.last(), saveState) { entry ->
receivedPop = true
popped = true
popEntryFromBackStack(entry, saveState, savedState)
}
if (!receivedPop) {
// The pop did not complete successfully, so stop immediately
break
}
}
if (saveState) {
if (!inclusive) {
// If this isn't an inclusive pop, we need to explicitly map the
// saved state to the destination you've actually passed to popUpTo
// as well as its parents (if it is the start destination)
generateSequence(foundDestination) { destination ->
if (destination.parent?.startDestinationId == destination.id) {
destination.parent
} else {
null
}
}.takeWhile { destination ->
// Only add the state if it doesn't already exist
!backStackMap.containsKey(destination.id)
}.forEach { destination ->
backStackMap[destination.id] = savedState.firstOrNull()?.id
}
}
if (savedState.isNotEmpty()) {
val firstState = savedState.first()
// Whether is is inclusive or not, we need to map the
// saved state to the destination that was popped
// as well as its parents (if it is the start destination)
val firstStateDestination = findDestination(firstState.destinationId)
// 찾은 NavDestination을 시작으로 시퀀스 생성
generateSequence(firstStateDestination) { destination ->
if (destination.parent?.startDestinationId == destination.id) {
destination.parent
} else {
null
}
// NavDestination ID를 저장하는 Map에서
// 해당 ID를 찾지 않은 동안만 원소를 모음(여기까지는 원소별 적용)
}.takeWhile { destination ->
// Only add the state if it doesn't already exist
!backStackMap.containsKey(destination.id)
// 모아진 원소를 저장된 값으로 Map 구성
}.forEach { destination ->
backStackMap[destination.id] = firstState.id
}
// And finally, store the actual state itself
backStackStates[firstState.id] = savedState
}
}
updateOnBackPressedCallbackEnabled()
return popped
}
* Navigator : Navigator defines a mechanism for navigating within an app.
Each Navigator sets the policy for a specific type of navigation, e.g. ActivityNavigator knows how to launch into destinations backed by activities using Context.startActivity.
Navigators should be able to manage their own back stack when navigating between two destinations that belong to that navigator. The NavController manages a back stack of navigators representing the current navigation stack across all navigators.
Each Navigator should add the Navigator.Name annotation to their class. Any custom attributes used by the associated destination subclass should have a name corresponding with the name of the Navigator, e.g., ActivityNavigator uses <declare-styleable name="ActivityNavigator">
4-1-1. navigator.popBackStackInternal(backQueue.last(), saveState) { entry ->
receivedPop = true
popped = true
popEntryFromBackStack(entry, saveState, savedState)
}
// NavController
private fun Navigator<out NavDestination>.popBackStackInternal(
popUpTo: NavBackStackEntry,
saveState: Boolean,
handler: (popUpTo: NavBackStackEntry) -> Unit = {}
) {
popFromBackStackHandler = handler
popBackStack(popUpTo, saveState)
popFromBackStackHandler = null
}
4-1-1-1. popBackStack(popUpTo, saveState) : Navigator가 가진 NavBackStackEntry list에서 param으로 넘겨받은 NavBackStackEntry를 찾아 NavigatorState pop 수행
// Navigator
@Suppress("UNUSED_PARAMETER")
public open fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) {
val backStack = state // NavigatorState
.backStack // StateFlow<List<NavBackStackEntry>>
.value // List<NavBackStackEntry>
check(backStack.contains(popUpTo)) {
"popBackStack was called with $popUpTo which does not exist in back stack $backStack"
}
val iterator = backStack.listIterator(backStack.size)
var lastPoppedEntry: NavBackStackEntry? = null
do {
if (!popBackStack()) {
// Quit early if popBackStack() returned false
break
}
lastPoppedEntry = iterator.previous() // NavBackStackEntry list 거꾸로 탐색
} while (lastPoppedEntry != popUpTo)
if (lastPoppedEntry != null) {
state.pop(lastPoppedEntry, savedState)
}
}
* NavBackStackEntry : Representation of an entry in the back stack of a androidx.navigation.NavController. The Lifecycle, ViewModelStore, and SavedStateRegistry provided via this object are valid for the lifetime of this destination on the back stack: when this destination is popped off the back stack, the lifecycle will be destroyed, state will no longer be saved, and ViewModels will be cleared.
4-1-1-2. state.pop(lastPoppedEntry, savedState) : param으로 넘어온 NavBackStackEntry를 찾을 때까지 back stack에 있는 모든 NavDestination pop 시키고, callback으로 NavController에 등록된 popFromBackStackHandler 호출(4-1에서 설정한 handler 수행)
// NavigatorState
public open fun pop(popUpTo: NavBackStackEntry, saveState: Boolean) {
backStackLock.withLock {
_backStack.value = _backStack.value.takeWhile { it != popUpTo }
}
}
4-1-2. popEntryFromBackStack(entry, saveState, savedState) : 넘겨준 entry에 해당하는 NavBackStackEntry는 BackStack 최상단에 있다는 것을 체크해 transition 중인지 혹은 자식을 가지고 있는지에 따라 연결 해제하고 뷰모델 클리어
// NavController
private fun popEntryFromBackStack(
popUpTo: NavBackStackEntry,
saveState: Boolean = false,
savedState: ArrayDeque<NavBackStackEntryState> = ArrayDeque()
) {
val entry = backQueue.last() // NavController에 등록된 NavBackStackEntry list에서
// 가장 마지막 NavBackStackEntry
check(entry == popUpTo) { // false면 람다 실행하고 예외 던지기
"Attempted to pop ${popUpTo.destination}, which is not the top of the back stack " +
"(${entry.destination})"
}
backQueue.removeLast() // 마지막 NavBackStackEntry 삭제
// 처음에 얻은 가장 마지막 NavBackStackEntry의 NavDestination과
// 연관된 이름의 Navigator 얻기
val navigator = navigatorProvider
.getNavigator<Navigator<NavDestination>>(entry.destination.navigatorName)
// NavController에서 관리하는 Map<Navigator, NavigatorState>에서 Navigator에 해당하는
// NavigatorState 얻기 (Navigator 내부에 NavigatorState가 있는데 굳이 또 Map으로
// 관리하는지 모르겠음)
val state = navigatorState[navigator]
// If we pop an entry with transitions, but not the graph, we will not make a call to
// popBackStackInternal, so the graph entry will not be marked as transitioning so we
// need to check if it still has children.
val transitioning = state?.transitionsInProgress? // transition중인 NavBackStackEntry set Flow
.value? // Flow 값 접근
.contains(entry) == true // 처음에 얻은 NavBackStackEntry가 포함되어 있는지
|| parentToChildCount.containsKey(entry) // Map<NavBackStackEntry, AtomicInteger>
// (내 생각에 key의 NavBackStackEntry가 가진
// 자식 수인듯)
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)
}
}
4-1-2-1. unlinkChildFromParent(entry) : entry로 넘어온 NavBackStackEntry 부모 NavBackStackEntry를 찾아 해당 NavBackStackEntry에서 parent 값을 지워버리고, 자식 수를 하나 감소시킴. 자식 수가 0일 때 해당 NavBackStackEntry의 Navigator를 얻어 NavigatorState의 transition 상태를 complete으로 변경하고, 자식 수를 카운트 하는 Map에서 삭제
// NavController
internal fun unlinkChildFromParent(child: NavBackStackEntry): NavBackStackEntry? {
val parent = childToParentEntries // <자식, 부모> 형태의
// Map<NavBackStackEntry, NavBackStackEntry>
.remove(child) ?: return null
val count = parentToChildCount[parent]?.decrementAndGet()
if (count == 0) {
val navGraphNavigator: Navigator<out NavGraph> =
_navigatorProvider[parent.destination.navigatorName]
navigatorState[navGraphNavigator]?.markTransitionComplete(parent)
parentToChildCount.remove(parent)
}
return parent
}
4-1-2-2. navigatorState[navGraphNavigator]?.markTransitionComplete(parent)
// NavControllerNavigatorState
override fun markTransitionComplete(entry: NavBackStackEntry) {
val savedState = entrySavedState[entry] == true
super.markTransitionComplete(entry)
entrySavedState.remove(entry)
if (!backQueue.contains(entry)) {
unlinkChildFromParent(entry)
// If the entry is no longer part of the backStack, we need to manually move
// it to DESTROYED, and clear its view model
if (entry.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
entry.maxLifecycle = Lifecycle.State.DESTROYED
}
if (backQueue.none { it.id == entry.id } && !savedState) {
viewModel?.clear(entry.id)
}
updateBackStackLifecycle()
_visibleEntries.tryEmit(populateVisibleEntries())
} else if (!this@NavControllerNavigatorState.isNavigating) {
updateBackStackLifecycle()
_visibleEntries.tryEmit(populateVisibleEntries())
}
// else, updateBackStackLifecycle() will be called after any ongoing navigate() call
// completes
}
4-2. restoreStateInternal(node.id, finalArgs, navOptions, navigatorExtras)
// NavController
private fun restoreStateInternal(
id: Int,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
): Boolean {
// popUpToInternal에서 pop 이후 backStackMap에 참조가능토록 저장해놓은 값이 없다면 return
if (!backStackMap.containsKey(id)) {
return false
}
// 저장해놓은 값이 있다면 참조
val backStackId = backStackMap[id]
// restore는 backQueue(전체 NavBackStackEntry 등록되어있음)에 들어가게 되면 재수행되지
// 않도록 복구를 위해 참조 ID값을 저장해놓았던 backStackMap에서 제거
// Clear out the state we're going to restore so that it isn't restored a second time
backStackMap.values.removeAll { it == backStackId }
// backStackStates에서도 해당 값 지우고 지운 값을 참조
val backStackState = backStackStates.remove(backStackId)
// backStackState는 NavController에서 전체적으로 관리되는 임시저장 상태의
// NavBackStackEntry를 참조할 수 있는 Map구조
// (pop되어 NavBackStackEntryState(임시저장된 상태의 NavBackStackEntry)의
// ArrayDeque를 NavBackEntry의 id로 저장한 Map)인데,
// 그걸 다시 Entry 상태로 전환함
// Now restore the back stack from its saved state
val entries = instantiateBackStack(backStackState)
// entries는 pop된 전체 NavBackStackEntryState가 NavBackStackEntry로 전환된 list
// Split up the entries by Navigator so we can restore them as an atomic operation
val entriesGroupedByNavigator = mutableListOf<MutableList<NavBackStackEntry>>()
entries.filterNot { entry ->
// Skip navigation graphs - they'll be added by addEntryToBackStack()
entry.destination is NavGraph
}.forEach { entry ->
// NavBackStackEntry는 NavDestination을 가지고 있고,
// NavDestination은 Navigator 명을 가지고 있어서
// 자신의 Navigator를 찾을 수 있다.
// pop 시점에 같은 NavBackStackEntry list 상에서 수행됐으므로,
// 그룹별로 나눌 때 직전 entry에서 획득할 수 있는 Navigator 명과 다르다면
// 새로운 그룹을 생성해 추가하고,
// 같다면 그 그룹에 추가하는 방식으로 간단히 조작할 수 있다.
val previousEntryList = entriesGroupedByNavigator.lastOrNull()
val previousNavigatorName = previousEntryList?.last()?.destination?.navigatorName
if (previousNavigatorName == entry.destination.navigatorName) {
// Group back to back entries associated with the same Navigator together
previousEntryList += entry
} else {
// Create a new group for the new Navigator
entriesGroupedByNavigator += mutableListOf(entry)
}
}
var navigated = false
// Navigator 그룹별로 나눈 list마다 수행
// Now actually navigate to each set of entries
for (entryList in entriesGroupedByNavigator) {
val navigator = _navigatorProvider.getNavigator<Navigator<NavDestination>>(
entryList.first().destination.navigatorName
)
var lastNavigatedIndex = 0
// 실제적으로 원하는 NavDestination으로의 이동을 마치고, handler 수행
navigator.navigateInternal(entryList, navOptions, navigatorExtras) { entry ->
navigated = true
// 복구된 back stack이라면 NavController의 back stack에 반영해주기 위해
// 이동한 entry와 마지막 navigate 지점이었던 entry 사이의 list를 참조
// If this destination is part of the restored back stack,
// pass all destinations between the last navigated entry and this one
// to ensure that any navigation graphs are properly restored as well
val entryIndex = entries.indexOf(entry)
val restoredEntries = if (entryIndex != -1) {
entries.subList(lastNavigatedIndex, entryIndex + 1).also {
lastNavigatedIndex = entryIndex + 1
}
} else {
emptyList()
}
// NavController의 back stack(backQueue)에 복구 후 이동된 경로에 있는 entry
// 모두 추가
addEntryToBackStack(entry.destination, args, entry, restoredEntries)
}
}
return navigated
}
4-2-1. instantiateBackStack(backStackState)
// NavController
private fun instantiateBackStack(
backStackState: ArrayDeque<NavBackStackEntryState>?
): List<NavBackStackEntry> {
val backStack = mutableListOf<NavBackStackEntry>()
// 가장 마지막에 등록된 NavBackStackEntry, 값이 없다면 가장 상단 NavGraph 참조
var currentDestination = backQueue.lastOrNull()?.destination ?: graph
// NavController에서 관리하는 Map<NavDestination ID, ArrayDeque<NavBackStackEntry>>에서
// 얻은 ArrayDeque<NavBackStackEntry>
backStackState?.forEach { state ->
// for문을 돌며 currentDestination과 같은 id를 가진 NavBackStackEntry
// 아니라면 null 반환
val node = currentDestination.findDestination(state.destinationId)
// node가 null이라면 예외 던지고 아래 메시지 출력
checkNotNull(node) {
val dest = NavDestination.getDisplayName(
context, state.destinationId
)
"Restore State failed: destination $dest cannot be found from the current " +
"destination $currentDestination"
}
// 해당 NavDestination을 가진 NavBackEntry를 생성해서
// mutableListOf<NavBackStackEntry>()에 추가
backStack += state.instantiate(context, node, hostLifecycleState, viewModel)
currentDestination = node
}
return backStack
}
4-2-1-1. currentDestination.findDestination(state.destinationId)
private fun NavDestination.findDestination(@IdRes destinationId: Int): NavDestination? {
// receiver의 id와 param으로 넘어온 id 값이 같다면 receiver 반환
if (id == destinationId) {
return this
}
// id가 같지 않을 때 receiver가 NavDestination이 아닌
// NavGraph(extends NavDestination) 라면 참조
// 아니라면 parent(NavGraph) 참조
val currentGraph = if (this is NavGraph) this else parent!!
// NavGraph 상에서 해당 id를 가진 NavDestination을 찾아 반환
return currentGraph.findNode(destinationId)
}
4-2-1-2. state.instantiate(context, node, hostLifecycleState, viewModel)
// NavBackStackEntryState
fun instantiate(
context: Context,
destination: NavDestination,
hostLifecycleState: Lifecycle.State,
viewModel: NavControllerViewModel?
): NavBackStackEntry {
val args = args?.apply {
classLoader = context.classLoader
}
return NavBackStackEntry.create(
context, destination, args,
hostLifecycleState, viewModel,
id, savedState
)
}
4-2-2. navigator.navigateInternal(entryList, navOptions, navigatorExtras) { entry ->
navigated = true
// If this destination is part of the restored back stack,
// pass all destinations between the last navigated entry and this one
// to ensure that any navigation graphs are properly restored as well
val entryIndex = entries.indexOf(entry)
val restoredEntries = if (entryIndex != -1) {
entries.subList(lastNavigatedIndex, entryIndex + 1).also {
lastNavigatedIndex = entryIndex + 1
}
} else {
emptyList()
}
addEntryToBackStack(entry.destination, args, entry, restoredEntries)
} : NavigatorState.push시 callback으로 넘겨받은 handler 수행할 수 있도록 설정한 상태에서 Navigator의 실제 이동 수행
private fun Navigator<out NavDestination>.navigateInternal(
entries: List<NavBackStackEntry>,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?,
handler: (backStackEntry: NavBackStackEntry) -> Unit = {}
) {
addToBackStackHandler = handler
navigate(entries, navOptions, navigatorExtras)
addToBackStackHandler = null
}
4-2-2-1. navigate(entries, navOptions, navigatorExtras) : 실제로 Navigator에서 원하는 페이지로 이동을 하며, 실제 동작 수행은 NavController가 위임, NavigatorState.push 후 NavController에 설정된 handler 수행
// Navigator
public open fun navigate(
entries: List<NavBackStackEntry>,
navOptions: NavOptions?,
navigatorExtras: Extras?
) {
entries.asSequence()
// 시퀀스 원소별 수행 내용
.map { backStackEntry ->
// 해당 Navigator가 처리할 수 있는 NavDestination의 타입인지 체크
val destination = backStackEntry.destination as? D ?: return@map null
// navigation graph안에서 주어진 destination으로 해당 Navigator를 이용해 이동
// destination을 반환
val navigatedToDestination = navigate(
destination, backStackEntry.arguments, navOptions, navigatorExtras
)
// 넘겨받은 값에 따라 처리하는데,
// NavDestination을 받으면 NavBackStackEntry로 변환
// 다른 경우에 해당 NavDestination으로 NavBackStackEntry 생성해 변환
when (navigatedToDestination) {
null -> null
destination -> backStackEntry
else -> {
state.createBackStackEntry(
navigatedToDestination,
navigatedToDestination.addInDefaultArgs(backStackEntry.arguments)
)
}
}
}.filterNotNull()
// 모아진 원소 이용
.forEach { backStackEntry ->
// NavigatorState에 해당 entry 삽입 수행
// NavController handler callback
state.push(backStackEntry)
}
}
4-2-3. addEntryToBackStack(entry.destination, args, entry, restoredEntries)
private fun addEntryToBackStack(
node: NavDestination,
finalArgs: Bundle?,
backStackEntry: NavBackStackEntry,
restoredEntries: List<NavBackStackEntry> = emptyList()
) {
// backStackEntry는 navigate 마친 시점의 entry(navigate 목적지)
val newDest = backStackEntry.destination
if (newDest !is FloatingWindow) {
// We've successfully navigating to the new destination, which means
// we should pop any FloatingWindow destination off the back stack
// before updating the back stack with our new destination
while (!backQueue.isEmpty() &&
backQueue.last().destination is FloatingWindow &&
popBackStackInternal(backQueue.last().destination.id, true)
) {
// Keep popping
}
}
// When you navigate() to a NavGraph, we need to ensure that a new instance
// is always created vs reusing an existing copy of that destination
val hierarchy = ArrayDeque<NavBackStackEntry>()
var destination: NavDestination? = newDest
if (node is NavGraph) {
do {
val parent = destination!!.parent
if (parent != null) {
val entry = restoredEntries.lastOrNull { restoredEntry ->
restoredEntry.destination == parent
} ?: NavBackStackEntry.create(
context, parent,
finalArgs, hostLifecycleState, viewModel
)
hierarchy.addFirst(entry)
// Pop any orphaned copy of that navigation graph off the back stack
if (backQueue.isNotEmpty() && backQueue.last().destination === parent) {
popEntryFromBackStack(backQueue.last())
}
}
destination = parent
} while (destination != null && destination !== node)
}
// Now collect the set of all intermediate NavGraphs that need to be put onto
// the back stack
destination = if (hierarchy.isEmpty()) newDest else hierarchy.first().destination
while (destination != null && findDestination(destination.id) == null) {
val parent = destination.parent
if (parent != null) {
val entry = restoredEntries.lastOrNull { restoredEntry ->
restoredEntry.destination == parent
} ?: NavBackStackEntry.create(
context, parent, parent.addInDefaultArgs(finalArgs), hostLifecycleState,
viewModel
)
hierarchy.addFirst(entry)
}
destination = parent
}
val overlappingDestination: NavDestination =
if (hierarchy.isEmpty())
newDest
else
hierarchy.last().destination
// Pop any orphaned navigation graphs that don't connect to the new destinations
while (!backQueue.isEmpty() && backQueue.last().destination is NavGraph &&
(backQueue.last().destination as NavGraph).findNode(
overlappingDestination.id, false
) == null
) {
popEntryFromBackStack(backQueue.last())
}
// The _graph should always be on the top of the back stack after you navigate()
val firstEntry = backQueue.firstOrNull() ?: hierarchy.firstOrNull()
if (firstEntry?.destination != _graph) {
val entry = restoredEntries.lastOrNull { restoredEntry ->
restoredEntry.destination == _graph!!
} ?: NavBackStackEntry.create(
context, _graph!!, _graph!!.addInDefaultArgs(finalArgs), hostLifecycleState,
viewModel
)
hierarchy.addFirst(entry)
}
// Now add the parent hierarchy to the NavigatorStates and back stack
hierarchy.forEach { entry ->
val navigator = _navigatorProvider.getNavigator<Navigator<*>>(
entry.destination.navigatorName
)
val navigatorBackStack = checkNotNull(navigatorState[navigator]) {
"NavigatorBackStack for ${node.navigatorName} should already be created"
}
navigatorBackStack.addInternal(entry)
}
backQueue.addAll(hierarchy)
// And finally, add the new destination
backQueue.add(backStackEntry)
// Link the newly added hierarchy and entry with the parent NavBackStackEntry
// so that we can track how many destinations are associated with each NavGraph
(hierarchy + backStackEntry).forEach {
val parent = it.destination.parent
if (parent != null) {
linkChildToParent(it, getBackStackEntry(parent.id))
}
}
}