Navigation - ActionBar
Navigation-ActionBar-Label
Navigation - BottomNavigationView
Navigation - MultipleBackStack
Navigation - safe-args
Before
2022.04.28 - [Android] - [Android/Kotlin] Navigation으로 Fragment 관리하기
2022.04.18 - [Android] - [Android/Kotlin] Jetpack Navigation이란?
0. 요구사항
3개의 탭(BottomNavigationView)에 각 프래그먼트를 연결하고, 프래그먼트마다 ActionBar에 다른 Label을 띄워주고, 첫 번째 프래그먼트가 아니라면 <-(뒤로가기)버튼이 생겨야 한다.
Issue
1. Actionbar에 Navigation을 연결했을 때 start Fragment끼리 이동했을 때 <-(뒤로가기) 버튼이 생성되던 문제
2. BottomNavigationView의 각 탭에서 한 뎁스 이상의 프래그먼트를 들어가고 다른 탭으로 이동한 후 다시 해당 탭으로 이동하면 BottomNavigationView의 탭이 바뀌지 않는 문제
1. Navigation-Actionbar
Actionbar 연결하기
Activity에서 간단한 코드로 Navigation을 이용해 ActionBar를 제어할 수 있다.
private lateinit var navController: NavController
private lateinit var appBarConfiguration: AppBarConfiguration
private fun initAppBar() {
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment
navController = navHostFragment.navController
appBarConfiguration = AppBarConfiguration(setOf(R.id.newsListFragment, R.id.newsCategoryFragment, R.id.savedNewsListFragment))
setupActionBarWithNavController(navController, appBarConfiguration)
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
정말 간단하다.
NavHostFragment가 가지고 있는 NavController와 AppbarConfiguration을 setupActionBarWithNavController()로 넣어주면 된다.
AppbarConfiguration은 setOf()로 바텀 탭의 첫 프래그먼트 3개를 넣어주면 각각의 프래그먼트가 시작 프래그먼트로 설정된다.
이 프래그먼트들은 형제 프래그먼트가 되어 시작 프래그먼트끼리 이동할 때는 Actionbar에 <-(뒤로가기) 버튼이 생성되지 않고, 각 탭에서 한 뎁스 이상 프래그먼트를 들어갔을 때에만 뒤로가기 버튼이 생성된다.
이렇게 첫 번째 Issue 해결!
Actionbar label
프래그먼트마다 Actionbar의 라벨이 달랐으면 한다.
초기에는 각 프래그먼트의 클래스 명으로 설정이 된다.
이를 바꾸려면 graph.xml에 선언된 fragment의 label을 바꾸어주면 되고 safe-args로 받은 값을 넣거나 String Resource를 넣을 수도 있다.
<top_graph.xml>
<fragment
android:id="@+id/newsDetailFragment"
android:name="com.yeongjin.news.view.newsdetails.NewsDetailFragment"
android:label="{title}">
<argument
android:name="news"
app:argType="com.yeongjin.news.data.model.News" />
<argument
android:name="title"
app:argType="string"
app:nullable="true" />
</fragment>
2. safe-args
위의 top_graph.xml 코드처럼 <argument> 선언된 것이 safe-args다.
<build.gradle - project>
buildscript {
dependencies {
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.1"
}
}
<build.gradle - module>
id 'androidx.navigation.safeargs'
<NewsListFragment.kt>
private val args by navArgs<NewsDetailFragmentArgs>()
private val savedNewsListViewModel: SavedNewsListViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.vm = savedNewsListViewModel
savedNewsListViewModel.setNews(args.news)
}
이렇게 간단히 사용할 수 있다.
safe-args 설정은 xml에 직접 코드로 작성해도 되지만 design에서 설정하는 것이 더 쉽다
이렇게 Fragment에 safe-args 인자를 추가하면 해당 프래그먼트로 이동하는 action에서는 해당하는 safe-args 인자들을 보내줘야 한다.
<BindingAdapter.kt>
// fragment에 작성해도 된다.
val action = NewsListFragmentDirections.actionNewsListFragmentToNewsDetailFragment(
data, data.title
)
view.findNavController().navigate(action)
3. Navigation-BottomNavigationView
BottomNavigationView 연결하기
<MainActivity.kt>
private lateinit var navController: NavController
private fun initBottomNavigation() {
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment
navController = navHostFragment.navController
NavigationUI.setupWithNavController(binding.bottomNavigationView, navController)
}
<activity_main.xml>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/navHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/bottomNavigationView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/main_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@color/purple_500"
app:menu="@menu/bottom_menu"
app:itemIconTint="@drawable/selector_nav_menu_color"
app:itemTextColor="@drawable/selector_nav_menu_color"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<bottom_menu.xml>
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/top_graph"
android:icon="@drawable/ic_news_24"
android:title="@string/top_news" />
<item
android:id="@+id/category_graph"
android:icon="@drawable/ic_category_24"
android:title="@string/categories" />
<item
android:id="@+id/saved_graph"
android:icon="@drawable/ic_save_24"
android:title="@string/saved" />
</menu>
<main_graph.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/main_graph"
app:startDestination="@id/top_graph">
<include app:graph="@navigation/top_graph"/>
<include app:graph="@navigation/category_graph"/>
<include app:graph="@navigation/saved_graph"/>
</navigation>
이전 글에서 BottomNavigationView를 Navigation으로 연결할 땐 bottom_menu.xml의 각각 Item에 시작 fragment의 id를 적어주었고, 하나의 grpah를 사용했었다. 하지만 이렇게 할 경우 BottomNavigationView의 Item이 적절하게 활성화되지 않는 위의 2번 Issue가 발생한다. 이에 대한 해결법이 위의 코드이다. 각각의 탭마다 graph를 따로 만들고, 그 graph들을 main_graph에 include로 묶어준다. 즉, 기존에 fragment 단위로 설정하던 것들을 graph단위로 바꾸어서 startDestination도 graph단위로, BottomNavigationView의 Item도 graph단위로 설정한다.
이제 Top 탭에서 두 번째 프래그먼트를 열고 다른 탭을 연 다음 다시 Top 탭을 누르면 Top 탭이 정상적으로 활성화 된다.
이때 Top 탭에서 활성화되어있는 프래그먼트는 첫 번째 프래그먼트일까 두 번째 프래그먼트일까?
이와 관련된 키워드가 Multiple Backstack이다.
Multiple Backstack?
위의 상황에서 Navigation 옛날 버전은 스택이 초기화되어 다시 Top 탭을 들어갔을 때 첫 번째 프래그먼트가 활성화된다.
즉, 탭을 이동할 때마다 이전 탭에서 했던 활동들이 초기화되어 상태를 유지하는 것이 어려웠다.
탭 별로 백스택을 따로 관리하는 Multiple Backstack을 위한 솔루션으로 architecture-components-samples 레포에서 BottomNavigationView Extensions를 제공해주었었고 이제는 Navigation Library에서 자체적으로 이런 기능을 제공해주기 때문에 Extension을 추가할 필요 없다. 2.4.0버전부턴 그냥 위 코드처럼 탭별로 graph를 따로 설정해주고 graph들을 포함한 container_graph를 BottomNavigationView에 연결만 하면 Multiple Backstack이 지원된다. 아래 레퍼런스와 특히 유튭 영상을 보면 Multiple Backstack이 적용되고 안 되고의 차이를 쉽게 확인할 수 있다.
Navigation 2.4.0-alpha01 부터 를 사용할 때 각 메뉴 항목의 상태가 저장되고 복원됩니다 setupWithNavController.
하단 탐색을 포함하는 포괄적인 예는 GitHub 의 Android 아키텍처 구성 요소 고급 탐색 샘플 을 참조하세요.
https://developer.android.com/guide/navigation/navigation-ui
https://developer.android.com/guide/navigation/multi-back-stacks?hl=ko
Result
https://github.com/dudwls901/wanted-pre-onboarding-Android
References
https://github.com/android/architecture-components-samples
https://medium.com/androiddevelopers/navigation-multiple-back-stacks-6c67ba41952f
https://www.youtube.com/watch?v=Covu3fPA1nQ&t=141s
'Android' 카테고리의 다른 글
[Android/Kotlin] Retrofit2은 어떻게 Interface의 구현 없이 사용할 수 있을까? (0) | 2022.12.11 |
---|---|
[Android/Kotlin] Coroutine은 왜 Light Thread이고 어떤 식으로 동작할까? (0) | 2022.09.19 |
[Android/Kotlin] Retrofit2(+Coroutine)은 왜 Main Thread에서 사용해도 될까? (0) | 2022.08.10 |
[Android] MVC, MVP, MVVM (0) | 2022.07.12 |
[Android] :app:kaptDebugKotlin 에러, m1 맥북 Room 라이브러리 에러 (0) | 2022.04.29 |
댓글