Navigation은 화면 이동과 관련된 액션을 쉽게 구현하도록 도와주는 Jetpack 라이브러리 중 하나이다.
기존에는 Fragment 간 전환 코드를 작성할 때 Fragment Manager 를 활용해야했다. 이러한 방식으로 구현을 하면 Fragment 백스택에 대한 관리, 전달할 파라미터 등 많은 요소를 코드로 직접 구현해주어야 했기 때문에 컴포넌트의 코드 양이 길어지고 난잡해지는 경우가 많았다.
Jetpack Navigation은 기존과 다르게 화면 이동, 스택 관리, 데이터 전달까지 쉽게 구현이 가능하다.

Navigation과 관련된 모든 정보를 가지고 있는 구성 요소로, 어떤 목적지들이 있는지, 이동 중 어떤 액션을 취할 것인지, 어떤 데이터를 넘겨줄 것인지에 대한 정보가 담겨져 있다.
XML 리소스를 작성할 수도 있고 코틀린 코드로 직접 생성할 수도 있으나 비쥬얼 에디터를 제공하기 때문에 XML로 주로 작성한다.
Navigation Graph 에 담겨져 있는 목적지로 화면을 표현하는 빈 컨테이너 공간이다.
findNavController().navigate(action)
앱간의 이동이나 전환을 관리해주는 객체로 화면 이동 이벤트가 발생하면 NavController에게 전달되어 NavController가 컨테이너인 NavHost에 적절한 화면을 표시한다.
implementation("androidx.navigation:navigation-fragment-ktx:2.5.1")
implementation("androidx.navigation:navigation-ui-ktx:2.5.1")
2.1. navigation 리소스 폴더 생성
res 폴더에 마우스 우클릭하여 New > Android resource file 을 선택한다.

resource type을 navigation이라고 지정하고 Navigation 그래프 파일 이름을 작성한다.

2.2. destination을 추가한다.
앞서 생성한 res/navigation/nav_graph.xml 파일에서 Design 탭을 클릭한다.
New Destination 아이콘을 클릭하여 화면을 추가한다.

2.3. startDestination을 지정해준다.
<navigation> 태그의 app:startDestination 속성을 반드시 지정해주어야 한다. startDestination으로 지정된 화면은 사용자가 앱을 처음 열 때 기본적으로 실행되는 화면이다.
<?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/nav_graph"
app:startDestination="@id/movieListFragment">
<fragment
android:id="@+id/movieListFragment"
android:name="com.example.MovieListFragment"
android:label="MovieListFragment" />
</navigation>
NavHostFragment는 다음과 같이 xml 레이아웃 파일에서 FragmentContainerView를 통해 구성된다.
app:NavGraph 속성에 NavHost와 연결될 Navigation 그래프를 작성해주면 된다.
<FrameLayout
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:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.movies.MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/nav_graph"/>
</FrameLayout>
4.1. action 추가하여 destination 연결
action은 destination 간의 논리적 연결이다.
Navigation Graph 파일의 Design 탭에서 드래그를 통해 만들 수 있고 화살표로 표시된다.

action을 통해 destination 간의 연결을 설정하면 xml파일에 다음과 같이 나타난다.
android:id 에 action 자신의 id가 있으며, app:destination 속성에는 fragment 혹은 activity의 id가 포함된다.
<fragment
android:id="@+id/movieListFragment"
android:name="com.shannon.moviemvvm.ui.movies.MovieListFragment"
android:label="MovieListFragment"
tools:layout="@layout/fragment_movie_list">
<action
android:id="@+id/action_movieListFragment_to_singleMovieFragment"
app:destination="@id/singleMovieFragment" />
</fragment>
4.2.NavController 를 활용하여 화면 이동
이제 코틀린 코드에서 아이템을 클릭했을 때 이동하는 코드를 작성한다.
MovieListFragment 안에 있는 RecyclerView 아이템 중 하나를 클릭하였을 때 SingleMovieFragment로 이동할 수 있도록 클릭 리스너를 추가한다.
그리고 그 안에 NavController 의 navigate() 메소드에서 어디로 갈지 action id를 입력한다.
override fun onSingleMovieClicked(movieId: Int?) {
findNavController().navigate(R.id.action_movieListFragment_to_singleMovieFragment , null)
}
4.3. 이동시 애니메이션 추가
방법1) xml에 추가하는 방법
<action
android:id="@+id/action_movieListFragment_to_singleMovieFragment"
app:destination="@id/singleMovieFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
방법2) 코드에 추가하는 방법
override fun onSingleMovieClicked(movieId: Int?) {
val options = navOptions {
anim {
enter = R.anim.slide_in_right
exit = R.anim.slide_out_left
popEnter = R.anim.slide_in_left
popExit = R.anim.slide_out_right
}
}
findNavController().navigate(
R.id.action_movieListFragment_to_singleMovieFragment , null, options
)
}
1) bundle 객체 활용
MovieListfragment에 아래와 같이 Bundle 객체를 만들고 navigate()의 파라미터로 넘겨주어 다음 화면에 값을 전달한다.
override fun onSingleMovieClicked(movieId: Int?) {
val bundle = bundleOf("movieId" to movieId)
findNavController().navigate(R.id.action_movieListFragment_to_singleMovieFragment , bundle)
}
전달받는 SingleMovieFragment에서는 getArguments() 메서드를 사용하여 값을 꺼낸다.
val movieId = arguments?.getInt("movieId") ?: 1
하지만 Bundle 객체를 사용하는 경우 다음과 같은 문제점이 있다.
2) Safe Args 활용
Safe Args 플러그인을 활용하면 화면간 데이터를 주고 받을 때 타입안정성을 보장해준다는 장점이 있다
Safe Args 플러그인을 추가하기 위해서는 먼저 프로젝트 build.gradle 파일에 다음을 작성한다.
dependencies {
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
그리고 앱의 build.gradle에서 플러그인을 추가해준다.
plugins {
id 'androidx.navigation.safeargs.kotlin'
}
nav_graph.xml에 argument를 추가해준다.
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/movieListFragment">
<fragment
android:id="@+id/movieListFragment"
android:name="com.shannon.moviemvvm.ui.movies.MovieListFragment"
android:label="MovieListFragment"
tools:layout="@layout/fragment_movie_list">
<action
android:id="@+id/action_movieListFragment_to_singleMovieFragment"
app:destination="@id/singleMovieFragment" />
</fragment>
<fragment
android:id="@+id/singleMovieFragment"
android:name="com.shannon.moviemvvm.ui.details.SingleMovieFragment"
android:label="SingleMovieFragment"
tools:layout="@layout/fragment_single_movie">
<argument
android:name="movieId"
app:argType="integer"
android:defaultValue="1"/>
</fragment>
</navigation>
데이터를 보내는 클래스에서는 (MovieListFragment) 이름 뒤에 Directions가 붙은 클래스가 자동으로 생성된다.
데이터를 전달하는 action도 id 이름 대로 내부에 메소드가 생성이 되어 전달할 값을 넣어주면 된다.
val action = MovieListFragmentDirections.actionMovieListFragmentToSingleMovieFragment(movieId)
findNavController().navigate(action)
데이터를 받는 (SingleMovieFragment) 에서는 Args 가 붙은 클래스가 자동으로 생성되어 데이터를 꺼내어 쓸 수 있다.
val safeArgs: SingleMovieFragmentArgs by navArgs()
val movieId = safeArgs.movieId