Jetpack Navigation 을 사용하여 Fragment 간에 전환하면 NavController 가 적절한 Navigator(예: FragmentNavigator) 를 선택하여 직접 FragmentManager 를 사용하여 FragmentTransaction 을 생성하고 각 Destination(NavBackStackEntry) 을 BackStack 에 push,pop 을 하기 때문에 개발자 입장에서는 굉장히 편리하게 화면 전환을 구현할 수 있습니다.
Chapter 01 Navigation Graph 의 각 Destination 을 호스팅하는 NavHostFragment
일반적으로 Jetpack Navigation 을 통해 Single Activity 에 여러 Fragment 를 사용할 때는 activity layout xml 에 FragmentContainerView 를 선언합니다. 이때 name 속성을 통해 FragmentContainerView 가 NavHostFragment 를 호스팅하도록 설정합니다. 이 NavHostFragment 는 하나의 FragmentContainerView 를 가지는 Fragment 이며, NavHostFragment 가 소유한 FragmentContainerView 에 Navigation Graph 의 각 Destination 을 호스팅하며 앱이 동작하게 됩니다. NavHostFramgent 의 각 Lifecycle 메서드에서 어떤 작업을 하는 지 하나씩 확인해보겠습니다.
첫번째 onAttach 에서는 parentFragmentManager(FragmentActivity 의 FragmentManager) 에 setPrimaryNavigationFragment 메서드를 호출하여 NavHostFragment 가 Back press 를 intercept 할 수 있도록 설정합니다. graphId 와 defaultNavHost 는 onInflate 에서 상위 Context 를 통해 속성을 파싱하여 설정합니다.
public open class NavHostFragment : Fragment(), NavHost {
//...
private var graphId = 0
private var defaultNavHost = false
@CallSuper
public override fun onAttach(context: Context) {
super.onAttach(context)
//defaultNavHost 속성을 true 로 설정한 경우 onInflate 메서드에서 defaultNavHost flag 를 true 로 설정합니다.
if (defaultNavHost) {
//상위 FragmentManager 즉, Activity 의 FragmentManager 에 현재 Fragment 를 Primary Navigation Fragment 로 설정하여 BackPress 를 인터셉트할 수 있도록 합니다.
parentFragmentManager.beginTransaction()
.setPrimaryNavigationFragment(this)
.commit()
}
}
//...
}
두번째 onCreate 에서는 NavHostFragment 의 NavController 인스턴스를 생성하고 Back press 시 호출될 Callback 을 등록하며 NavController 가 Navigation 에 사용할 Navigation Graph 를 생성하는 등의 작업을 합니다.
@CallSuper
public override fun onCreate(savedInstanceState: Bundle?) {
//NavHostFragment 내부에서 소유할 NavController 인스턴스를 생성합니다.
var context = requireContext()
navHostController = NavHostController(context)
navHostController!!.setLifecycleOwner(this)
//Back Press 했을 경우 호출될 콜백을 등록합니다.(콜백은 NavController 의 popBackStack 메서드를 호출합니다.)
while (context is ContextWrapper) {
if (context is OnBackPressedDispatcherOwner) {
navHostController!!.setOnBackPressedDispatcher(
(context as OnBackPressedDispatcherOwner).onBackPressedDispatcher
)
// Otherwise, caller must register a dispatcher on the controller explicitly
// by overriding onCreateNavHostController()
break
}
context = context.baseContext
}
// Set the default state - this will be updated whenever
// onPrimaryNavigationFragmentChanged() is called
navHostController!!.enableOnBackPressed(
isPrimaryBeforeOnCreate != null && isPrimaryBeforeOnCreate as Boolean
)
isPrimaryBeforeOnCreate = null
navHostController!!.setViewModelStore(viewModelStore)
onCreateNavHostController(navHostController!!)
var navState: Bundle? = null
if (savedInstanceState != null) {
navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE)
if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
defaultNavHost = true
parentFragmentManager.beginTransaction()
.setPrimaryNavigationFragment(this)
.commit()
}
graphId = savedInstanceState.getInt(KEY_GRAPH_ID)
}
if (navState != null) {
// Navigation controller state overrides arguments
navHostController!!.restoreState(navState)
}
//onInflate 에서 설정한 Navigation Graph 의 id 를 NavController 가 참조할 Navigation Graph 로 설정합니다.
if (graphId != 0) {
// Set from onInflate()
navHostController!!.setGraph(graphId)
} else {
// See if it was set by NavHostFragment.create()
val args = arguments
val graphId = args?.getInt(KEY_GRAPH_ID) ?: 0
val startDestinationArgs = args?.getBundle(KEY_START_DESTINATION_ARGS)
if (graphId != 0) {
navHostController!!.setGraph(graphId, startDestinationArgs)
}
}
// We purposefully run this last as this will trigger the onCreate() of
// child fragments, which may be relying on having the NavController already
// created and having its state restored by that point.
super.onCreate(savedInstanceState)
}
onCreateView 에서는 FragmentContainerView 인스턴스를 하나 생성하고 id 를 설정한 후 반환합니다.
FragmentContainerView 의 id 는 Resource 에 미리 지정된 R.id.nav_host_fragment_container 를 참조합니다.
public override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//NavHostFragment 는 FragmentContainerView 하나만 가진 Fragment 입니다.
val containerView = FragmentContainerView(inflater.context)
containerView.id = containerId
return containerView
}
onViewCreated 에서는 onCreateView 에서 반환한 FragmentContainerView 에 tag 를 설정합니다. key는 R.id.nav_controller_view_tag 이며 value 는 onCreate 에서 생성한 NavController 입니다.
public override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//onCreateView 에서 반환한 View 가 ViewGroup 이 아닌 경우 IllegalStateException 예외를 발생시킵니다.
check(view is ViewGroup) { "created host view $view is not a ViewGroup" }
//onCreateView 에서 반환한 View 인 FragmentContainerView 의 tag 에 R.id.nav_controller_view_tag ID 로 NavController 를 설정합니다.
Navigation.setViewNavController(view, navHostController)
if (view.getParent() != null) {
viewParent = view.getParent() as View
if (viewParent!!.id == id) {
Navigation.setViewNavController(viewParent!!, navHostController)
}
}
}
'android > Jetpack' 카테고리의 다른 글
6. ViewModelStore 와 ViewModelStoreOwner (0) | 2022.11.18 |
---|---|
Room DB 와 RoomTrackingLiveData (0) | 2022.11.03 |
5-1. Jetpack Navigation의 구성요소 (0) | 2022.10.09 |
4-2 단방향 데이터 바인딩, 양방향 데이터 바인딩 (0) | 2022.10.08 |
4-1 DataBinding (1) | 2022.10.07 |