기존에는 Fragment를 전환하려면 supportFragmentManager로 FragmentManager 인스턴스를 반환 받고 add나 replace 를 통해 Fragment를 교체하고 commit으로 직접 transaction을 실행했어야 했다.
그런데, Jetpack이 도입 되면서 화면 전환에 FragmentManager 직접 사용하는 것보다 Navigation Library를 사용하는 것을 권장하고 있다.
Navigation graph는 네비게이션 에디터를 사용하거나 xml을 직접 수정함으로써 앱에서 사용 할 모든 화면과 연관 관계, 이동 방법을 정의하게 된다.
Navigation host는 Navigation graph에서 정의한 Fragment를 화면에 표시하기 위한 컨테이너이다.
컨테이너를 배치할 레이아웃 파일에 FragmentContainerView를 추가하면 된다.(android:name="androidx.navigation.fragment.NavHostFragment")
이 때, 안드로이드 네임 속성에 의해서 View가 navhost Fragment에 연결되게 된다.
app:defaultNavHost 속성에 의해 네비게이션 호스트가 시스템의 백버튼 입력을 가로채서 이전 화면으로 전환할 수 있게 한다.
app:navGraph="@navigation/nav_graph"로 네비게이션 그래프를 지정해 주게 된다.
Navigation controller는 화면 전환을 수행하는 컨트롤러이다. 모든 네비게이션 호스트는 하나의 네비게이션 컨트롤러를 가지게 된다.
프레그먼트를 전환하기 위해서는 네비게이션 호스트로부터 네비게이션 컨트롤러의 인스턴스를 얻어서 네비게이션 메소드를 실행하게 된다.
// 네비게이션 컨트롤러의 인스턴스를 프레그먼트 안에서 얻기 위함
navController = findNavController()
// 네비게이션 컨트롤러의 인스턴스를 액티비티 안에서 얻기 위함
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
기존에는 프레그먼트 사이에서 데이터를 전달하는 데에는 key-value 타입의 Bundle을 사용했었다. 하지만, Bundle은 타입 안정성을 보장하지 않는 한계가 있다. 이를 개선하기 위해
NavigationComponent
에서는SafeArgument
개념을 도입해서 데이터를 안전하게 전달 할 수 있도록 한다.
action
이 시작되는 대상의 이름에 Directions
라는 접미어가 붙은 클래스argument
를 전달하는데 사용한 action
의 이름과 동일한 이름의 클래스Args
라는 접미어가 붙은 클래스 // 아래와 같이 인수를 설정하고 navigate 메소드에 전달함으로써 데이터를 전달 할 수 있게 된다.
val amount = binding.amountTv.text.toString().toInt()
val action = SpecifyAmountFragmentDirections.confirmationAction(amount)
view.findNavController().navigation(action)
dependencies {
...
// Navigation
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
}
res > New > Android Resource File
을 클릭하여 파일을 만들어주는데 File name을 입력하고 Resource type을 NAvigation
으로 설정한다.FragmentContainerView
를 추가한다. Navigation host
가 되어서 Fragment들을 보여주는 표시를 이 곳에서 관리한다. <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/frame_layout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/bottom_navigation_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
</FrameLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="0dp"
android:layout_height="66dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/bottom_navigation_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
class MainActivity : AppCompatActivity() {
private val binding: ActivityMainBinding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
setUpJetpackNavigation()
}
private fun setUpJetpackNavigation() {
// Navigation Controller 인스턴스를 취득
val host = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = host.navController
/*
* setupWithNavController를 사용하여 Bottom Navigation View를 Navigation Controller와 연결
* Navigation이 Fragment 전환을 수행한다.
*/
binding.bottomNavigationView.setupWithNavController(navController)
}
}
// project build.gradle
plugins {
...
id("androidx.navigation.safeargs.kotlin") version "2.5.0" apply false
}
// app build.gradle
plugins {
...
id("androidx.navigation.safeargs.kotlin")
}
데이터를 전달 받을 DetailFragment를 추가하고, Bottom Navigation의 탭에서 선택시 DetailFragment로 이동 및 데이터 전달을 수행하기 위해 화살표를 만들어 연결한다.
위에서 생성한 DetailFragment에서 Argument를 받을 수 있도록 설정한다.
Arguments 탭에서 플러스 버튼을 클릭하면 다음과 같은팝업이 나타나는데, 전달 받을 데이터의 Name과 Type을 설정한다.
// HomeFragment
private fun initView() {
binding.btSendData.setOnClickListener {
// DetailFragment로 데이터 전달 및 이동
val action = HomeFragmentDirections.actionFragmentHomeToFragmentDetail("test")
findNavController().navigate(action)
}
}
// DetailFragment
private fun initView() {
// HomeFragment 로부터 전달 받은 데이터
val id = args.id
Log.d("TAG", "HomeFragment: $id") // test
}