Navigation Component 분석하기
Navigation component를 이용하여 BottonNavigationView에서 사용할 Navigation graph입니다. home.xml, order_list.xml, like.xml, map.xml, my_info.xml을 include 합니다.
BottomNavigationView의 menu item들 입니다. Navigation graph와 android:id를 일치시켜 주어야합니다.
위 그림과 같이 BottonNavigationView에서 사용하는 Navigation graph의 xml들을 확인할 수 있습니다. (home.xml, order_list.xml, like.xml, map.xml, my_info.xml)
현재 home.xml을 제외한 graph에 단일 Fragment만 존재해서 home.xml의 코드만 보겠습니다.
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/home"
app:startDestination="@id/homeMainFragment">
<fragment
android:id="@+id/homeMainFragment"
android:name="com.example.YUmarket.screen.home.homemain.HomeMainFragment"
android:label="HomeMainFragment" >
<action
android:id="@+id/action_homeMainFragment_to_homeFragment"
app:destination="@id/homeFragment" />
</fragment>
<fragment
android:id="@+id/homeFragment"
android:name="com.example.YUmarket.screen.home.HomeFragment"
android:label="HomeFragment" >
<argument
android:name="goToTab"
app:argType="com.example.YUmarket.model.homelist.category.HomeListCategory"
android:defaultValue="TOWN_MARKET" />
</fragment>
</navigation>
home.xml 전체 코드입니다. 차근히 나눠서 알아보겠습니다.
<fragment
android:id="@+id/homeMainFragment"
android:name="com.example.YUmarket.screen.home.homemain.HomeMainFragment"
android:label="HomeMainFragment" >
<action
android:id="@+id/action_homeMainFragment_to_homeFragment"
app:destination="@id/homeFragment" />
</fragment>
위와 같이 home.xml에는 homeMainFragment와 homeFragment가 있습니다. homeMainFragment에서 homeFragment로 action을 통해서 이동을 정의 할 수 있습니다.
<fragment
android:id="@+id/homeFragment"
android:name="com.example.YUmarket.screen.home.HomeFragment"
android:label="HomeFragment" >
<argument
android:name="goToTab"
app:argType="com.example.YUmarket.model.homelist.category.HomeListCategory"
android:defaultValue="TOWN_MARKET" />
</fragment>
HomeFragment에 argument를 정의해 줍니다. HomeMainFragment에서 HomeFragment로 이동시에 표시한 탭의 Data값을 HomeListCategory에서 얻어옵니다. argument로 Data 값에 맞는 상태 탭으로 이동합니다.
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainer"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/bottomNav"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:navGraph="@navigation/navigation_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNav"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/bottom_navigation_menu" />
android:name="androidx.navigation.fragment.NavHostFragment"
android:name은 요소에 어떤 NavHost가 구현될지를 정의해주며 위에서는 Fragment NavHost임을 정의해주었습니다.
그래서 FragmentContainerView에 android:name을 NavHostFragment로 명시를 하여 containerFragment를 설정해주고 app:navGraph에 사용할 Navigation graph를 설정합니다. app:defaultNavHost를 true로 하여 back button의 동작을 intercept하도록 하였습니다. (좀 더 생각 필요)
private val navController by lazy {
val hostContainer = supportFragmentManager
.findFragmentById(R.id.fragmentContainer) as NavHost
hostContainer.navController
}
override fun initViews() = with(binding) {
...
bottom_nav.setupWithNavController(navController)
...
}
NavHostFragment에서 NavController를 lazy하게 선언합니다.
Activity의 extension 함수인 Activity.findNavController(viewId: Int)를 사용 못하는 이유는 FragmentContainerView를 이용하여 NavHostFragment를 생성해서 입니다. 대신에 Fragment.findNavController 로 사용이 가능하기도 합니다.
(아래에서 Fragment.findNavController 를 다루겠습니다)
그리고 NavController를 이용하여 BottomNavigationView를 정의하여 알맞은 Fragment가 출력되도록 하였습니다.
When creating the NavHostFragment using FragmentContainerView or if manually adding the NavHostFragment to your activity via a FragmentTransaction, attempting to retrieve the NavController in onCreate() of an Activity via Navigation. findNavController(Activity, @IdRes int) will fail. You should retrieve the NavController directly from the NavHostFragment instead. [1]
Android Developers 공식 페이지에서는 위와 같이 NavHostFragment에서 NavController를 직접(directly) 사용하는 것으로 설명합니다.
private val navControllers by lazy {
supportFragmentManager
.findFragmentById(R.id.fragmentContainer)
?.findNavController()
}
override fun initViews() = with(binding) {
...
bottomNav.setupWithNavController(navControllers!!)
...
}
하지만 공식 홈페이지의 설명과 다르게 위와 같이 사용할 수도 있습니다.
override fun initViews() {
...
//더보기를 누르면 마켓을 List로 띄워주는 Fragment로 이동
showMoreTextView.setOnClickListener {
findNavController().navigate(
HomeMainFragmentDirections.actionHomeMainFragmentToHomeFragment())
}
...
setCategoryButtonListener()
}
private fun setCategoryButtonListener() = with(binding) {
val navController = findNavController()
val buttonList = listOf(
foodCategoryListButton, martCategoryListButton, serviceCategoryListButton,
fashionCategoryListButton, accessoryCategoryListButton, etcCategoryListButton
)
categories.forEachIndexed { index, homeListCategory ->
buttonList[index].setOnClickListener {
navController.navigate(
HomeMainFragmentDirections
.actionHomeMainFragmentToHomeFragment(homeListCategory)
)
}
}
}
버튼별로 HomeFragment에서 알맞은 탭을 보여줄 수 있도록 알맞은 argument를 넘겨주고 HomeMainFragment에서 HomeFragment로 이동하도록 OnClickListener를 설정해주었습니다. showMoreTextView의 action에 argument의 기본값이 TOWN_MARKET으로 설정되어 있기 때문에 아무것도 넘겨주지 않도록 했습니다.
private val args by navArgs<HomeFragmentArgs>()
override fun observeData() = with(binding) {
activityViewModel.locationData.observe(viewLifecycleOwner) {
when (it) {
is MainState.Success -> {
initViewPager()
//View Pager의 현재 Item을 SafeArgs로 받아온 Tab으로 설정
viewPager.setCurrentItem(args.goToTab.ordinal, false)
}
}
}
}
by navArgs()로 argument를 lazy하게 가져오도록 하였습니다. Fragment 이동시에 argument로 받은 HomeListCategory의 값을 이용하여 viewPager.currentItem = args.goToTab.ordinal로 알맞은 카테고리의 화면을 출력합니다.
(추가내용)
- viewPager.currentItem = args.goToTab.ordinal
viewPager.currentItem = args.goToTab.ordinal로 ViewPager의 현재 아이템을 설정하였습니다. 이렇게 아이템을 설정하게되면 smoothScroll이 기본값인 true로 설정됩니다.
- viewPager.setCurrentItem(args.goToTab.ordinal, false)
ViewPager의 currentItem을 설정할 때 smoothScroll을 false로 설정하여 해결하였습니다.
이로써 우리는 Navigation Component에 대한 기본적인 내용과 더불어 이 개념을 활용하는 여러가지 방법을 실제로 사용해 보았습니다. 다음 내용으로 돌아오겠습니다. 감사합니다.
https://developer.android.com/guide/navigation/navigation-getting-started#navigate.