본문 바로가기
Android

[Android/Kotlin] Jetpack Navigation -BottomNavigationView, Actionbar (feat. Multiple BackStack)

by 옹구스투스 2022. 9. 12.
반응형

 


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

 

GitHub - dudwls901/wanted-pre-onboarding-Android: 프리 온보딩 사전 과제 뉴스 앱

프리 온보딩 사전 과제 뉴스 앱. Contribute to dudwls901/wanted-pre-onboarding-Android development by creating an account on GitHub.

github.com

References​

https://github.com/android/architecture-components-samples

 

GitHub - android/architecture-components-samples: Samples for Android Architecture Components.

Samples for Android Architecture Components. . Contribute to android/architecture-components-samples development by creating an account on GitHub.

github.com

https://medium.com/shdev/jetpack-navigation-4-%EB%B0%B1-%EC%8A%A4%ED%83%9D-%EA%B4%80%EB%A6%AC-8ada98c4bd55

 

Jetpack Navigation-4 백 스택 관리

BottomNavigaionView를 사용할 때 메뉴 아이템마다 독립적인 백 스택을 사용할 수 있도록 구현합니다.

medium.com

https://medium.com/androiddevelopers/navigation-multiple-back-stacks-6c67ba41952f

 

Navigation: Multiple back stacks

Welcome to another article in the second MAD Skills series on Navigation! In this article we’ll take a look at a highly requested feature…

medium.com

https://www.youtube.com/watch?v=Covu3fPA1nQ&t=141s 

 

반응형

댓글