[Android] Jetpack Navigation

강승구·2023년 9월 27일

Navigation은 화면 이동과 관련된 액션을 쉽게 구현하도록 도와주는 Jetpack 라이브러리 중 하나이다.

기존에는 Fragment 간 전환 코드를 작성할 때 Fragment Manager 를 활용해야했다. 이러한 방식으로 구현을 하면 Fragment 백스택에 대한 관리, 전달할 파라미터 등 많은 요소를 코드로 직접 구현해주어야 했기 때문에 컴포넌트의 코드 양이 길어지고 난잡해지는 경우가 많았다.

Jetpack Navigation은 기존과 다르게 화면 이동, 스택 관리, 데이터 전달까지 쉽게 구현이 가능하다.


  • Fragment 트랜잭션 처리
  • 손쉬운 화면 이동 처리
  • 손쉬운 애니메이션 구현 지원
  • 딥링크 구현 및 처리 지원
  • Safe args 와 함께 안전한 데이터 전달 지원
  • Bottom Navigation, Navigation Drawer 와 같은 요소들을 손쉽게 구현할 수 있도록 지원
  • ViewModel 을 이용, 그래프 내의 컴포넌트 간에 UI 관련 데이터를 공유할 수 있도록 지원
  • 안드로이드 스튜디오의 네비게이션 에디터를 통해 네비게이션 그래프를 한 눈에 보고 쉽게 편집할 수 있도록 지원

Navigation과 관련된 모든 정보를 가지고 있는 구성 요소로, 어떤 목적지들이 있는지, 이동 중 어떤 액션을 취할 것인지, 어떤 데이터를 넘겨줄 것인지에 대한 정보가 담겨져 있다.
XML 리소스를 작성할 수도 있고 코틀린 코드로 직접 생성할 수도 있으나 비쥬얼 에디터를 제공하기 때문에 XML로 주로 작성한다.

Navigation Graph 에 담겨져 있는 목적지로 화면을 표현하는 빈 컨테이너 공간이다.

findNavController().navigate(action)

앱간의 이동이나 전환을 관리해주는 객체로 화면 이동 이벤트가 발생하면 NavController에게 전달되어 NavController가 컨테이너인 NavHost에 적절한 화면을 표시한다.


  1. 특정 경로를 따라 이동할지, 특정 목적지로 직접 이동할 것인지 결정 후 NavController 에게 요청한다.
  2. NavController 는 Navigation Graph 내에서 적절한 목적지를 찾아 NavHost 에 표현한다.

사용법

1. 의존성 추가

implementation("androidx.navigation:navigation-fragment-ktx:2.5.1")
implementation("androidx.navigation:navigation-ui-ktx:2.5.1")

2. Navigation graph 만들기

2.1. navigation 리소스 폴더 생성

res 폴더에 마우스 우클릭하여 New > Android resource file 을 선택한다.

img

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

img

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

img

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>

3. NavHost 설정

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. NavController 를 사용하여 화면(destination)간의 이동 설정

4.1. action 추가하여 destination 연결

action은 destination 간의 논리적 연결이다.
Navigation Graph 파일의 Design 탭에서 드래그를 통해 만들 수 있고 화살표로 표시된다.

img

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로 이동할 수 있도록 클릭 리스너를 추가한다.
그리고 그 안에 NavControllernavigate() 메소드에서 어디로 갈지 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
       )
    }

5. 화면간 데이터 주고 받기

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 객체를 사용하는 경우 다음과 같은 문제점이 있다.

  1. Type mismatch error
    만약 fragment A가 string을 전달했는데 fragment B가 integer를 요청한다면 런타임 에러가 발생할 수 있다.
  2. missing key error
    fragment b에서 bundle에 없는 key를 요청한다면 null을 반환하여 런타임 에러가 발생할 수 있다.

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.xmlargument를 추가해준다.

<?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가 붙은 클래스가 자동으로 생성된다.
데이터를 전달하는 actionid 이름 대로 내부에 메소드가 생성이 되어 전달할 값을 넣어주면 된다.

val action = MovieListFragmentDirections.actionMovieListFragmentToSingleMovieFragment(movieId)
findNavController().navigate(action) 

데이터를 받는 (SingleMovieFragment) 에서는 Args 가 붙은 클래스가 자동으로 생성되어 데이터를 꺼내어 쓸 수 있다.

        val safeArgs: SingleMovieFragmentArgs by navArgs()
        val movieId = safeArgs.movieId
profile
강승구

0개의 댓글