Library 기준

androidx.navigation:navigation-fragment-ktx:2.5.3

androidx.navigation:navigation-ui-ktx:2.5.3

FragmentManager 를 통한 Transaction

  • add: 호스트 Activity의 생명주기에 Fragment 생명주기 추가, add된 Fragment는 onAttach ~ onResume까지 호출
    • Add a fragment to the activity state.
  • remove: onPause ~ onDetach까지 호출, Fragment가 메모리에서 제거됨.
    • Remove an existing fragment. If it was added to a container, its view is also removed from that container.
  • replace: replace() 함수 인자로 지정된 Fragment를 제외한 나머지 모든 프래그먼트가 remove (나머지 Fragment는 onDetach 까지 호출, 지정된 Fragment는 onAttach ~ onResume )
  • show / hide: 기본적으로 add된 Fragment를 대상으로 View를 보이게 하거나 감춤.(visibility 변경)
  • attach: 대상 Fragment의 onCreateView ~ onResume 까지 호출
    • Detach the given fragment from the UI. This is the same state as when it is put on the back stack: the fragment is removed from the UI
  • detach: 대상 Fragment의 onPause ~ onDestroyView 까지 호출
    • Re-attach a fragment after it had previously been detached from the UI with detach(android.app.Fragment). This causes its view hierarchy to be re-created, attached to the UI, and displayed.

Jetpack Navigation 의 장점

  • Fragment의 관계를 resource.xml 파일로 한눈에 볼 수 있다.
  • FragmentManager를 통한 Transaction 처리에 쓰이는 코드를 줄일 수 있다.
  • Fragment 간에 data 전달을 safe-args를 통해 할 수 있다.
  • deep link 처리

navigate 시 Fragment 생명주기

navControllerbackQueue 변수로 backStack을 확인할 수 있다.

1
2
3
4
5
6
7
8
// navController.kt 내부 코드

/**
 * Retrieve the current back stack.
 * @return The current back stack.
 */
@get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public open val backQueue: ArrayDeque<NavBackStackEntry> = ArrayDeque()

navigate() 함수 호출시, FragmentTransactionreplace()가 호출된다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// FragmentNavigator.kt 내부 코드
@Navigator.Name("fragment")
public open class FragmentNavigator(
    private val context: Context,
    private val fragmentManager: FragmentManager,
    private val containerId: Int
) : Navigator<Destination>() {
    // ... 

    private fun createFragmentTransaction(
        entry: NavBackStackEntry,
        navOptions: NavOptions?
    ): FragmentTransaction {
        val destination = entry.destination as Destination
        //..
        val frag = fragmentManager.fragmentFactory.instantiate(context.classLoader, className)
        frag.arguments = args
        val ft = fragmentManager.beginTransaction()
        //..
        ft.replace(containerId, frag)
        //..
        return ft
    }
}

상황 설정

  • bottomMenu: GameFragment, SettingFragment 이고,
  • action: SettingFragment -> WebViewFragment 인 상황
  • GameFragment -> SettingFragment -> WebViewFragment ->(뒤로가기 버튼) SettingFragment 의 경우

bottomNavi.png

Logging 결과

  • backStack 에서 없어지면(pop되면), onDestoryView ~ onDetach 까지 호출
  • backStack 에 존재하면, onDestoryView 까지만 호출, 다시 backStack 최상단에 특정 Fragment가 존재하면, onCreateView 부터 호출
  • A -> B로 naviagte() 시, B가 Create 되고, A가 Destory 됨. (선 Create, 후 Destory)
13:43:37.780 GameFragment         onCreate
13:43:37.802 GameFragment         onCreateView
13:43:37.802 GameFragment         [com.k031.fruitcardgame:id/game_nav_graph, com.k031.fruitcardgame:id/GameFragment]
13:43:39.586 SettingFragment      onCreate
13:43:39.586 SettingFragment      onCreateView
13:43:39.586 SettingFragment      [com.k031.fruitcardgame:id/game_nav_graph, com.k031.fruitcardgame:id/GameFragment, com.k031.fruitcardgame:id/SettingFragment]
13:43:39.740 GameFragment         onDestroyView
13:43:42.369 WebViewFragment      onCreate
13:43:42.369 WebViewFragment      onCreateView
13:43:42.369 WebViewFragment      [com.k031.fruitcardgame:id/game_nav_graph, com.k031.fruitcardgame:id/GameFragment, com.k031.fruitcardgame:id/SettingFragment, com.k031.fruitcardgame:id/WebViewFragment]
13:43:42.560 SettingFragment      onDestroyView
13:43:44.910 SettingFragment      onCreateView
13:43:44.910 SettingFragment      [com.k031.fruitcardgame:id/game_nav_graph, com.k031.fruitcardgame:id/GameFragment, com.k031.fruitcardgame:id/SettingFragment]
13:43:44.919 WebViewFragment      onDestroyView
13:43:44.920 WebViewFragment      onDestroy

navigation popup

a -> b -> c -> a 로 group 연결

a_b_c_navi.png

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

<fragment
        android:id="@+id/c"
        android:name="com.example.myapplication.C"
        android:label="fragment_c"
        tools:layout="@layout/fragment_c">

    <action
            android:id="@+id/action_c_to_a"
            app:destination="@id/a"

            app:popUpTo="@+id/a"
            app:popUpToInclusive="true"/>
</fragment>

case 별 backStack 현황

  • ... -> a -> b -> c 까지, backStack 현황 = [..., a, b, c]
  • app:popUpTo="@+id/a" 가 없다면, a -> b -> c -> a 까지, backStack 현황 = [..., a, b, c, a]
  • app:popUpTo="@+id/a" 가 있다면, a -> b -> c -> a 까지, backStack 현황 = [..., a, a]
  • app:popUpTo="@+id/a" 있고, app:popUpToInclusive=“false” 이면, a -> b -> c -> a 까지, backStack 현황 = [..., a, a]
  • app:popUpTo="@+id/a" 있고, app:popUpToInclusive=“true” 이면, a -> b -> c -> a 까지, backStack 현황 = [..., a]

결론

  • popUpTo 속성으로 특정 Fragment를 지정하면, 그 사이의 NavBackStackEntry은 backStack에서 제거된다.
  • popUpToInclusive 속성을 true로 설정해주면, backStack popUpTo로 지정된 NavBackStackEntry은 제거된다.
  • popUpToInclusive 값을 지정하지 않으면, popUpToInclusive=false와 같다.

Reference