문제 : 위 사진(카카오톡)과 같이 바텀 메뉴 클릭 시
최초 클릭 시에는 프래그먼트가 생성되어 화면이 로딩되고, 이후 클릭 시에는 기존 생성된 프래그먼트가 유지되었으면 한다.
그러나 기본적인 Jetpack Navigation + Bottom Navigation View의 경우에는 바텀 메뉴를 클릭 할 때마다 프래그먼트가 재생성된다.
1 - 최초 생성
2 - 한번 더 BookFragment 바텀 메뉴 클릭
3 - 다른 바텀 메뉴로 갔다가 다시 BookFragment 메뉴 클릭
위와 같이 어떤 경우에서든지 메뉴를 클릭하기만 하면 해당 메뉴와 연결된 프래그먼트가 재생성된다.
1 - 최초 생성의 경우에만 프래그먼트가 생성되고, 2와 3의 경우에는 기존 프래그먼트를 hide - show 하는 방향으로 하여 카카오톡처럼 최초 생성의 상태를 유지하여 UX를 증대시키고자 한다.
해결 : 여기저기 찾아보던 중 , 좋은 블로그를 통해 좋은 코드를 찾아 이를 활용하였다. 출처는 글 마지막에 남긴다.
* 해당 코드는 Navigation 2.3.5 버전까지만 동작하며, 이후 버전은 제대로 동작하지 않는다.
(댓글로 알려주신 이지훈님 감사드립니다!)
새로운 FragmentNavigator 클래스를 만든다.
@Navigator.Name("keep_state_fragment") 부분은 해당 내비게이터의 이름으로,
graph.xml에서 <keep_state_fragment>와 같은 형태로 쓰일 태그의 이름을 설정한다고 보면 된다.
해당 FragmentNavigator의 navigate를 override하여 재정의함으로써 기존의 프래그먼트 재생성 문제를 해결한다.
@Navigator.Name("keep_state_fragment")
class KeepStateNavigator(
private val context: Context,
private val manager: FragmentManager,
private val containerId: Int
) : FragmentNavigator(context, manager, containerId) {
override fun navigate(
destination: Destination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Navigator.Extras?
): NavDestination? {
// destination tag
val tag = destination.id.toString()
val transaction = manager.beginTransaction()
var initialNavigate = false
val currentFragment = manager.primaryNavigationFragment
// primaryNavigationFragment가 존재하면 기존 primaryFragment hide 처리 (재생성 방지)
if (currentFragment != null) {
transaction.hide(currentFragment)
} else {
initialNavigate = true
}
var fragment = manager.findFragmentByTag(tag)
// 최초로 생성되는 fragment
if (fragment == null) {
// add로 fragment 최초 생성 (add)
val className = destination.className
fragment = manager.fragmentFactory.instantiate(context.classLoader, className)
transaction.add(containerId, fragment, tag)
} else {
// 이미 생성되어 있던 fragment라면 show
transaction.show(fragment)
}
// destination fragment를 primary로 설정
transaction.setPrimaryNavigationFragment(fragment)
// transaction 관련 fragment 상태 변경 최적화
transaction.setReorderingAllowed(true)
transaction.commitNow()
return if (initialNavigate) {
destination
} else {
null
}
}
}
기존에 존재하는 primary fragment가 있다면 hide 처리를 하여 fragment가 재생성되지 않게 한다.
destination fragment가 fragmentManager에 존재한다면 기존에 hide 처리되어 있던 fragment를 show 하여 재생성이 아닌
상태유지하게 하고, 존재하지 않는다면 최초 생성이므로 fragmentFactory를 통해 instance 생성 후 add한다.
해당 내비게이터를 MainActivity 단에서 navController에 추가하고, bottom Navigation과 navController를 연결한다.
val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_container) as NavHostFragment
val navController = navHostFragment.navController
// KeepStateNavigator navController에 추가
val navigator = KeepStateNavigator(this, navHostFragment.childFragmentManager, R.id.main_container)
navController.navigatorProvider.addNavigator(navigator)
navController.setGraph(R.navigation.nav_graph)
// 바텀 네비게이션 뷰와 navController 연결
binding.bottomNav.setupWithNavController(navController)
기존 Navigation을 사용 할 때에는 activity_main.xml 단의 FragmentContainerView에 바로 app:navGraph="@navigation/nav_graph"와 같이 graph를 설정하였으나,
직접 커스터마이징한 내비게이터를 추가해야 한다면 위와 같이 하드코딩으로 내비게이터를 추가한 후에 setGraph로 그래프를 추가해주어야 내비게이터가 정상적으로 그래프에 반영된다. xml 단에서 바로 navGraph를 설정한다면 navigator를 찾을 수 없다는 오류를 발생시킨다.
마지막으로 navigation graph에서 <fragment> 태그로 설정되어 있던 Fragment Navigator를 커스터마이징한 keep_state_fragment로 변경한다.
// nav_graph.xml 일부분
<keep_state_fragment android:id="@+id/books_fragment"
android:name="com.hunseong.bookbak.ui.books.BooksFragment"
tools:layout="@layout/fragment_books">
</keep_state_fragment>
<keep_state_fragment android:id="@+id/report_fragment"
android:name="com.hunseong.bookbak.ui.report.ReportFragment"
tools:layout="@layout/fragment_report">
</keep_state_fragment>
<keep_state_fragment android:id="@+id/favorite_fragment"
android:name="com.hunseong.bookbak.ui.favorite.FavoriteFragment"
tools:layout="@layout/fragment_favorite">
</keep_state_fragment>
<keep_state_fragment android:id="@+id/my_fragment"
android:name="com.hunseong.bookbak.ui.my.MyFragment"
tools:layout="@layout/fragment_my">
</keep_state_fragment>
위와 같은 설정을 마치고 나면 기대했던 대로 1 - 최초 생성 이후에는 바텀 메뉴 재클릭, 다른 메뉴에서 돌아왔을 때 모두
프래그먼트가 재생성되지 않는다.
블로그 출처 : https://youngest-programming.tistory.com/651?category=898095
코드 출처 : https://github.com/STAR-ZERO/navigation-keep-fragment-sample
예제 코드 : https://github.com/HunSeongPark/BookBak