[Android, Kotlin] Bottom Navigation View + Jetpack Navigation 바텀 메뉴 클릭 시 프래그먼트 재생성 막기
Android

[Android, Kotlin] Bottom Navigation View + Jetpack Navigation 바텀 메뉴 클릭 시 프래그먼트 재생성 막기

카카오톡 화면전환

문제 : 위 사진(카카오톡)과 같이 바텀 메뉴 클릭 시

최초 클릭 시에는 프래그먼트가 생성되어 화면이 로딩되고, 이후 클릭 시에는 기존 생성된 프래그먼트가 유지되었으면 한다.

 

그러나 기본적인 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

 

[안드로이드] Jetpack Navigation 사용시 프래그먼트 재생성 되는 문제 해결 (android bottom navigation fragmen

https://github.com/STAR-ZERO/navigation-keep-fragment-sample GitHub - STAR-ZERO/navigation-keep-fragment-sample Contribute to STAR-ZERO/navigation-keep-fragment-sample development by creating an ac..

youngest-programming.tistory.com

 

 

코드 출처 : https://github.com/STAR-ZERO/navigation-keep-fragment-sample

 

GitHub - STAR-ZERO/navigation-keep-fragment-sample

Contribute to STAR-ZERO/navigation-keep-fragment-sample development by creating an account on GitHub.

github.com

 

예제 코드 : https://github.com/HunSeongPark/BookBak

 

GitHub - HunSeongPark/BookBak: 북박 - 도서 한줄 리뷰, 독후감 작성 앱 📖

북박 - 도서 한줄 리뷰, 독후감 작성 앱 📖. Contribute to HunSeongPark/BookBak development by creating an account on GitHub.

github.com