기존의 fragmentManager 를 사용해서 프래그먼트의 백스택을 관리할 때는
getBackStackEntryAt(index: 0) 이라는 함수를 통해 백스택내에 존재하는 최하단에 위치한 프래그먼트의 대한 정보를 얻을 수 있었다.
https://developer.android.com/reference/android/app/FragmentManager
내가 개발하고 있는 프로젝트에서는 Jetpack 의 Navigation을 사용하여 fragmentManager 를 직접 사용하지않으므로 (deprecated 되었기도 했고, 안쓴지 너무 오래되었다.. 다 까먹은거 같은데)
Navigation에서도 같은 역할을 수행하는 메소드가 존재하는지 궁금했다.
그렇게 Navigation 코드를 까보았지만 딱 원하는 그런 함수(백스택의 최하단의 프래그먼트를 반환하는 함수)는 존재하지 않은 듯 했다.
(발견 하지 못한 걸 수도 있으니 있으면 알려주세요)
하지만 최하단의 있는 프래그먼트의 id를 가져올 수 있는 방법은 존재했다.
findNavController().backQueue[1].destination.id
navigation 에서는 의외로 백스택을 'backQueue' 라는 네이밍으로 관리하고 있었다. 충격
Type 은 ArrayDeque Type 이다.
@get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public open val backQueue: ArrayDeque<NavBackStackEntry> = ArrayDeque()
@set:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public var destination: NavDestination,
백스택내에 가장 탑에 위치한 프래그먼트를 (정확히는 NavBackStackEntry) 가져오는 코드는 그래서 다음과 같이 구성되어있다.
// The topmost NavBackStackEntry.
// Returns:
// the topmost entry on the back stack or null if the back stack is empty
public open val currentBackStackEntry: NavBackStackEntry?
get() = backQueue.lastOrNull()
// The previous visible NavBackStackEntry.
// This skips over any NavBackStackEntry that is associated with a NavGraph.
// Returns:
// the previous visible entry on the back stack or null if the back stack has less than two visible entries
public open val previousBackStackEntry: NavBackStackEntry?
get() {
val iterator = backQueue.reversed().iterator()
// throw the topmost destination away.
if (iterator.hasNext()) {
iterator.next()
}
return iterator.asSequence().firstOrNull { entry ->
entry.destination !is NavGraph
}
}
SharedFlow 와 StateFlow 를 사용한 코드도 존재하는데 이에 대해선 추후 학습을 통해서 파악해보도록 하겠다.
private val _currentBackStackEntryFlow: MutableSharedFlow<NavBackStackEntry> =
MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
/**
* A [Flow] that will emit the currently active [NavBackStackEntry] whenever it changes. If
* there is no active [NavBackStackEntry], no item will be emitted.
*/
public val currentBackStackEntryFlow: Flow<NavBackStackEntry> =
_currentBackStackEntryFlow.asSharedFlow()
private val _visibleEntries: MutableStateFlow<List<NavBackStackEntry>> =
MutableStateFlow(emptyList())
/**
* A [StateFlow] that will emit the currently visible [NavBackStackEntries][NavBackStackEntry]
* whenever they change. If there is no visible [NavBackStackEntry], this will be set to an
* empty list.
*
* - `CREATED` entries are listed first and include all entries that have been popped from
* the back stack and are in the process of completing their exit transition
* - `STARTED` entries on the back stack are next and include all entries that are running
* their enter transition and entries whose destination is partially covered by a
* `FloatingWindow` destination
* - The last entry in the list is the topmost entry in the back stack and is in the `RESUMED`
* state only if its enter transition has completed. Otherwise it too will be `STARTED`.
*
* Note that the `Lifecycle` of any entry cannot be higher than the containing
* Activity/Fragment - if the Activity is not `RESUMED`, no entry will be `RESUMED`, no matter
* what the transition state is.
*/
public val visibleEntries: StateFlow<List<NavBackStackEntry>> =
_visibleEntries.asStateFlow()
쨌든 다시 본문으로 돌아가서 그러면 왜 index 0 번째가 아닌 1번째일까?
확인을 위해 다음과 같이 로그를 찍어보았다
Timber.d("${findNavController().backQueue[0].destination.id}")
Timber.d("${findNavController().backQueue[0].destination.label}")
Timber.d(findNavController().backQueue[0].destination.navigatorName)
Timber.d(findNavController().backQueue[0].destination.displayName)
Timber.d(findNavController().backQueue[0].destination.route)
Timber.d("${findNavController().backQueue[1].destination.id}")
Timber.d("${findNavController().backQueue[1].destination.label}")
Timber.d(findNavController().backQueue[1].destination.navigatorName)
Timber.d(findNavController().backQueue[1].destination.displayName)
Timber.d(findNavController().backQueue[1].destination.route)
D/LogoutDialogFragment: 2131296698
D/LogoutDialogFragment: null
D/LogoutDialogFragment: navigation
D/LogoutDialogFragment: com.depromeet.sloth:id/nav_main
D/LogoutDialogFragment: 2131296898
D/LogoutDialogFragment: TodayLessonFragment
D/LogoutDialogFragment: fragment
D/LogoutDialogFragment: com.depromeet.sloth:id/today_lesson
10 개의 로그를 찍었는데 8개 밖에 찍히지 않은 것은 마음에 안들지만 파악하는데 무리는 없었다.
TodayLessonFragment 는 navigation의 startDestination으로 설정한 프래그먼트(destination) 이며 이는 BackQueue[1] 번 인덱스에 대한 로그에서 확인할 수 있었다.
BackQueue[0] 번 인덱스에는 로그에서 확인할 수 있는 것 처럼 navGraph 자체가 들어가 있는 듯 하다.
수정)
덧글의 workspace 님께서 알려주시길
navigation 의 최신 버전에서는 backQueue에 직접 접근할 수 없어지기 때문에 navigation version 2.6.x 이상에서는 다음과 같은 방법으로 백스택 최하단의 프래그먼트에 접근할 수 있다
findNavController().currentBackStack.value[1].destination.id
수정전 (2.6.0 버전 미만)
/**
* Retrieve the current back stack.
*
* @return The current back stack.
* @hide
*/
@get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public open val backQueue: ArrayDeque<NavBackStackEntry> = ArrayDeque()
수정 후 (2.6.0 버전 이상)
private val backQueue: ArrayDeque<NavBackStackEntry> = ArrayDeque()
private val _currentBackStack: MutableStateFlow<List<NavBackStackEntry>> =
MutableStateFlow(emptyList())
/**
* Retrieve the current back stack.
*
* @return The current back stack.
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public val currentBackStack: StateFlow<List<NavBackStackEntry>> =
_currentBackStack.asStateFlow()
실제 사용 예시)
private fun navigateToLogin() {
val action = NavMainDirections.actionGlobalToLogin()
val navOptions = NavOptions.Builder()
//.setPopUpTo(findNavController().backQueue[1].destination.id, true)
.setPopUpTo(findNavController().currentBackStack.value[1].destination.id, true)
.build()
findNavController().navigate(action, navOptions)
}
참고)
https://velog.io/@ejjjang0414/안드로이드-Fragment-manager
https://velog.io/@changhee09/안드로이드-Fragment-Manager-Fragment-Transaction
navigation 2.6.0 에서 backQueue에 직접 접근이 제한되며, currentBackStack이라는 대체재가 주어집니다. 아래 링크 내용 살펴보세요~~
https://android-review.googlesource.com/c/platform/frameworks/support/+/2147462/1/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt