본문 바로가기
Android

[Android/Kotlin] Navigation으로 Fragment 관리하기

by 옹구스투스 2022. 4. 28.
반응형

 


Navigation으로 Fragment 이동 관리하기

Fragment 간 데이터 이동은?

백 스택 관리는?


Before

2022.04.18 - [Android] - [Android/Kotlin] Jetpack Navigation이란?

 

[Android/Kotlin] Jetpack Navigation이란?

Jetpack Navigation이란? Jetpack Navigation이란? 네비게이션이란 하나의 화면에서 다른 화면으로 이동하는 것을 말한다. 기존에는 intent나 fragment Transaction으로 네비게이션(화면 이동)을 구현하였는데,..

ongveloper.tistory.com

 

0. 요구사항

하나의 액티비티에 프래그먼트 A, B가 있다.

프래그먼트 A의 RecyclerView의 Item에 Image를 누르면 클릭한 Item의 정보를 가진 채

Fragment B로 화면이 전환되어야 한다.

 

 

1. Navigation으로 Fragment 이동 관리하기

기존에 Fragment Transaction으로 관리하던 화면 이동을 Navigation으로 Migration한다.

Before

화면 이동에 관한 전체적인 소스를 보자.

MainActivity의 onCreate에서 fragmentA로 화면을 초기화하고, 아래 replaceFragment 함수는 접근 제한자를 디폴트로 두어 다른 Fragment 클래스에서도 접근 가능하게 한다.

FragmentA에 생성자에 mainActivity 객체를 넘겨준다.

MainActivity

프래그먼트A 안에 RecyclerView Adapter의 생성자에 리스너를 받아오고,

리사이클러뷰가 있는 Fragment에 photo라는 데이터를 PhotoViewModel로 감싸서 콜백을 넘긴다.

현재 PhotoViewModel을 DataBinding하여 화면에 데이터를 뿌려주고 있다.

RecyclerView Adapter

콜백을 받아 mainAcvtivity의 replaceFragment 함수를 호출하여 화면 이동을 구현한다.

이때 프래그먼트B에 생성자로 PhotoViewModel 데이터를 넘긴다.

FragmentA

프래그먼트B에서 받아온 photoViewModel 데이터를 DataBinding으로 뷰에 뿌려준다.

FragmentB

 

여기까지가 이전 코드이다.

Fragment간 데이터를 공유하는 방법은 많은데 지금처럼 mainActivity를 거치는 것보단, Activty에서 프래그먼트 컨테이너 아이디를 프래그먼트에 공유하고, 프래그먼트에서 parenfFragment.beginTransaction로 처리하는 것이 더 좋을 것 같다. Navigation으로 이전할 생각에 대충 짜 놓은 거다..! 사실 이 상황엔 activityViewModels을 사용하는 게 제일 편하다.

 

After

이번엔 위의 코드를 Navigation으로 바꾸어 화면 이동을 구현해보자!

 

우선 Navigation 종속성을 추가한다.

최신 버전은 안드로이드 디벨로퍼에서 확인할 것!

// Jetpack Navigation Kotlin
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'

Navigation xml을 만들어 준다.

자동으로 navigation 디렉토리가 생성되고, 그 안에 xml이 들어간다.

nav_graph의 Design에서 프래그먼트A,B를 추가해 주고, 화살표로 연결한다.

nav_graph.xml

디자인에서 선을 연결하면 아래와 같이 이에 대한 코드가 자동으로 작성된다.

프래그먼트A는 프래그먼트B로, 프래그먼트B는 프래그먼트A로 이동하는 action을 가지고 있다는 의미이다.

navigation 태그 안에 id와 startDestination이 설정되어 있는데,

startDestination은 시작 프래그먼트를 의미한다.

nav_graph.xml

이제 액티비티의 fragmentContainerView에서 nav_graph를 연결하고 이를 name 속성을 통해 NavHostFragment로 만들어 준다. name과 defaultNavHost를 지정하면 디폴트 프래그먼트가 nav_graph.xml의 디폴트 startDestination으로 설정된다.

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/fragmentContainer"
    android:name="androidx.navigation.fragment.NavHostFragment"
    app:defaultNavHost="true"
    app:navGraph="@navigation/nav_graph"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />​

 

이제 프래그먼트A에서 navController를 만든다.

공식문서에 따르면 요런 식으로 만들 수 있다.

        navController = Navigation.findNavController(view)
//        navController = view.findNavController()

그리고 아까 FragmentA에서 이미지를 눌렀을 때 mainActivity의 replaceFragment 함수를 호출하는 것이 아닌 navController.navigate()함수로 이동하면 된다. 이때 번들로 photoViewModel을 넘길 수 있다.

PhotoViewModel은 Serializable을 상속받아야 한다

    override fun onPhotoClickListener(photoViewModel: PhotoViewModel) {
        //legacy
//        mainActivity.replaceFragment(PhotoFragment.newInstance(photoViewModel))
        //navigation
        navController.navigate(
            R.id.action_galleryFragment_to_photoFragment,
            bundleOf(Pair("photoViewModel", photoViewModel))
        )
    }

이후 프래그먼트 B에서 번들로 photoViewModel을 받는다.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val bundlePhotoViewModel = arguments?.getSerializable("photoViewModel") as PhotoViewModel
    binding.photoViewModel = bundlePhotoViewModel
}

Navigation으로 화면 이동 구현이 완료되었다..!

이제 Fragment에 생성자로 mainActivity를 넘기지 않아도 되고 MainActivity의 코드도 간결해졌다.

2. 백 스택 관리는?

위의 과정까지 완료했으면 프래그먼트B에서 뒤로 가기 클릭 시 프래그먼트A로 이동된다.

프래그먼트A에서 뒤로 가기를 누르면? 종료된다.

현재 이 프로젝트에서 프래그먼트A가 시작 화면이다.

보통 앱을 만들 때 시작 화면에서 뒤로 가기를 누르면 바로 앱이 종료되지 않고 두 번 연속으로 눌러야 바로 종료되는 것을 볼 수 있다. 이 부분을 구현해보자.

 

MainActivity의 onCreate에서 navController를 초기화해준다.

val navHostFragment =supportFragmentManager.findFragmentById(R.id.fragmentContainer) as NavHostFragment

navController = navHostFragment.navController

주석 친 부분이 Navigation 사용하기 이전 코드이다.

onBackPressed() 함수를 오버라이딩하여 내가 원하는 대로 뒤로 가기 동작을 커스텀할 수 있다.

onCreate에서 초기화한 navController로 현재 프래그먼트의 id를 가져올 수 있다.

현재 프래그먼트의 id가 시작 프래그먼트의 id와 같다면 뒤로 가기 두 번 누르면 종료시키고,

다르다면(현재 프래그먼트가 프래그먼트B라면) 원래 onBackPressed 그대로 동작한다.

원래 onBackPressed 동작은 뒤로 가기 했을 때 Activity 스택에서 하나 pop 될 텐데,

Navigation을 사용하면 Navigation의 스택이 우선적으로 pop된다.

이 부분은 정확히 어떻게 동작하는지 더 알아봐야겠다.

 

private var backButtonTime = 0L

	override fun onBackPressed() {
        val currentTime = System.currentTimeMillis()
        val gapTime = currentTime - backButtonTime
//        val currentFragment = supportFragmentManager.findFragmentById(galleryFragment.id)
        //갤러리 화면이면 뒤로가기 시 앱 종료
        if(navController.currentDestination!!.id==R.id.galleryFragment){
            if(gapTime in 0..2000){
                //2초 안에 두 번 뒤로가기 누를 시 앱 종료
                finishAndRemoveTask()
            }
            else{
                backButtonTime = currentTime
                Toast.makeText(this, "뒤로가기 버튼을 한 번 더 누르면 종료됩니다.", Toast.LENGTH_SHORT).show()
            }
        }
        //갤러리 화면이 아니면
        else{
//            replaceFragment(galleryFragment)
            super.onBackPressed()
        }
    }

}

 

3. BottomNavigationView와 Navigation 연결

 

추가적으로 바텀네비게이션뷰와 네비게이션을 연결하는 법이다.

nav_graph에 design으로 원하는 프래그먼트를 올려놓는다. 선을 연결하진 않는다.(각 프래그먼트에 action을 정의하지 않는다.) 여기서 주의할 점은 bottom_nav_menu의 item의 id와 nav_graph의 fragment id는 같아야 한다.

 

 

이후 액티비티에서 세 줄의 코드만 작성하면 끝

val navHostFragment = supportFragmentManager.findFragmentById(R.id.navHost) as NavHostFragment

val navController = navHostFragment.navController

//네비게이션과 바텀네비 연결
NavigationUI.setupWithNavController(binding.bottomNavigationView, navController)

 

 

Next

  1. SafeArgs 플러그인으로 Navigation 이동, 데이터 공유 바꾸기
  2. Navigation 사용 시 스택 내부 동작 확인하기
  3. 내가 만든 앱 Fragment Transaction에서 Navigation으로 Migration하기 

References​

https://developer.android.com/guide/navigation/navigation-navigate?hl=ko 

 

대상으로 이동  |  Android 개발자  |  Android Developers

대상으로 이동 대상으로 이동하는 것은 NavController 객체를 사용하여 실행되며 이 객체는 NavHost 내에서 앱 탐색을 관리합니다. 각 NavHost에는 그에 상응하는 자체 NavController가 있습니다. NavController

developer.android.com

 

 

 

반응형

댓글