sw engineering/android

Android - Navigation Component 소개

monotics 2021. 2. 24. 02:28

Android Navigation은 Fragment들 사이를 요리조리 쉽게 왔다 갔다 할 수 있도록 도와준다. 기존에는 Fragment들 사이에 argument를 전달하려면 번거로운 작업이었는데 이것도 쉽게 처리해준다. 또한 GUI 툴까지 제공하니 보다 손쉽게 이 모든 것을 다룰 수 있다. Navigation Component가 Google I/O '18에서 소개되었으니 벌써 3년이나 된 기술이다. 이 포스팅에서는 Android Navigation을 프로젝트에 어떻게 적용하는지 알아본다. Android Navigation에 대한 자세한 정보는 안드로이드 가이드를 참조하면 된다. 이번 포스팅에서는 가이드에서 Getting started 섹션을 많이 참고하였다.

 

Fig 1. Navigation Component

 


Navigation Component

Navigation은 크게 3가지로 구성되어있다.

 • Navigation graph : Navigation 관련 정보들을 가진 xml 리소스이다. 이 리소스는 code 혹은 graph editor를 통해 navigation graph를 구성하고 속성들을 작성할 수 있다. destination들 사이에 action도 정의할 수 있다.
 • NavHost : 레이아웃에 한자리를 차지하고 있는 비어있는 방이라 생각하면 된다. 이 빈 공간에 navigation graph에 있는 destination들을 교체해가면서 표시해준다.
 • NavController : NavHost내의 destination의 전환을 제어한다.

Navigation Component가 하는 일

 • Fragment 전환을 처리한다.
 • Up과 Back에 대해 기본적인 동작을 할 수 있도록 처리해준다.
 • destination이 교체될 때 애니메이션과 전환 효과를 위해 표준화된 리소스를 제공한다.
 • 딥 링크의 구현과 처리를 다룬다.
 • 최소한의 작업으로 Navigation Drawer와 Bottom Navigation 같은 Navigation UI 패턴의 처리를 포함한다.
 • safe args plugin을 사용하면 destination들 사이의 이동과 이들 간의 데이터를 전달 시 타입 안정성을 제공해준다.
 • ViewModel 지원 - destination들 사이의 UI 관련 데이터를 공유하기 위해 navigation graph에서 ViewModel을 살펴볼 수 있도록 해준다.


1. Project 생성

테스트 프로젝트(NavTest)를 하나 만들어보면서 Android Navigation Component을 시작해보자.

🚀 File -> New -> New Project...

New Project 창이 나타나는데 여기에 아래와 같이 입력하고 Finish 버튼을 클릭하면 프로젝트가 생성된다.

   Name : NavTest

   Package name : com.abc.navtest

   Save location : D:\temp\NavTest

 

Fig 2. New Project

 


2. Navigation 관련 dependency 추가

프로젝트에서 Navigation을 지원하려면 app 모듈의 build.gradle 파일에 아래 dependency 들을 추가해야 한다.

dependencies {
    def nav_version = "2.3.2"

    // Java language implementation
    implementation "androidx.navigation:navigation-fragment:$nav_version"
    implementation "androidx.navigation:navigation-ui:$nav_version"

    // Kotlin
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

    // Feature module Support
    implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"

    // Testing Navigation
    androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"

    // Jetpack Compose Integration
    implementation "androidx.navigation:navigation-compose:1.0.0-alpha05"
    
    ...

3. navigation graph 생성

navigation graph는 destination들과 action들을 포함한다. destination은 앱 내에서 서로 다른 고유의 내용물을 담고 있는 곳이며 fragment, activity 등이 여기에 해당한다. action은 사용자의 동작에 따라 destination들 사이를 어떻게 연결하는지를 나타낸다. 프로젝트에 navigation graph를 추가해보자. 이때 Project가 'Android'로 되어있어야 한다.

🚀 app/res에서 마우스 우클릭 > New > Android Resource File

 

Fig 3. navigation graph 리소스 추가

 

New Resource File 창이 나타나면 다음과 같이 입력한다.

 • File name : "my_nav_graph" 입력

 • Resource type : Navigation 선택

나머지 필드는 자동으로 채워진다. OK 버튼 클릭하면 res 아래에 navigation 디렉터리가 생기고 File name에서 설정한 "my_nav_graph"으로 navigation graph 리소스 파일(my_nav_graph.xml)이 생성된 것을 볼 수 있다.

 

Fig 4. my_nav_graph.xml 생성

 

아래는 이렇게 생성된 my_nav_graph.xml의 내용이다. <navigation>은 루트 element이며 이 아래에 <fragment>와 <action> element가 온다. 지금은 비어있지만 code 혹은 graph editor로 destination(<fragment>)과 action(<action>)을 추가해 나가면 된다.

<?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/my_nav_graph">

</navigation>

4. 액티비티에 NavHost 추가

Navigation Host는 비어있는 컨테이너라고 생각하면 된다. 이 비어있는 곳에서 destination들이 교체(swapping)되면서 내비게이션이 이루어진다고 보면 된다. Navigation Host는 NavHost 인터페이스를 구현해야 한다. Navigation Component의 기본 NavHost 구현체인 NavHostFragment는 fragment destination들의 교체(swapping)를 처리다.

🧁 Navigation component는 하나의 메인 액티비티 내에 여러 fragment destination들을 가지는 앱을 위해 설계되었다. 메인 액티비티는 navigation graph와 연결되고 필요에 따라 destination들을 교체하는 역할을 하는 NavHostFragment를 포함한다. 만약 여러 개의 activity destination을 가지는 앱이 있다면, 각 액티비티는 자신들만의 navigation graph를 가진다.

NavHost를 액티비티에 추가하는 방법은 2가지가 있는데 편한 방법으로 사용하면 된다.

🥨 Code editor 사용 (방법 1)

activity_main.xml 파일을 열어보면 기본으로 이미 추가되어있는 <TextView>가 있다. 이 부분을 제거하고 아래와 같이 NavHostFragment를 추가한다.

<?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">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/my_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/my_nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>

여기에 나오는 몇 가지 중요한 속성들을 살펴보면 다음과 같다.

 • android:name : NavHost를 구현한 class의 이름을 나타낸다. (NavHostFragment)
 • app:navGraph : NavHostFragment와 관련된 navigation graph 리소스를 나타낸다. (my_nav_graph)
 • app:defaultNavHost="true" : NavHostFragment가 시스템 back button을 가로챌지를 정한다. 하나의 NavHost만 default로 설정될 수 있다. 만약 NavHost가 여러 개이면 그중에 하나만 default NavHost로 설정이 가능하다.

 

🥨 Layout Editor 사용 (방법 2)

res/layout/activity_main.xml 파일 더블 클릭

우측 상단에 있는 'Design'을 선택하여 Layout Editor 창으로 변경
'Palette' 패널에서 Containers > NavHostFragment를 선택
선택한 NavHostFragment를 액티비티 영역 위로 드래그
Navigation Graphs 창이 나타남
my_nav_graph를 선택
OK 버튼을 클릭

 

Fig 5. Layout Editor로 전환
Fig 6. Main Activity에 NavHostFragment 추가

 


5. navigation graph에 destination 추가

destination은 이미 만들어진 fragment 혹은 activity를 통해 만들 수 있다. 또한 Navigation Editor를 사용하여 새로운 destination을 만들 수도 있다. 지금은 아니지만 나중에 fragment 혹은 activity를 교체한다면 placeholder로 미리 만들어둘 수도 있다. 여기서는 새로운 destination을 만들어 보기로 한다. 그러려면 다음과 같은 순서로 하면 된다.

① my_nav_graph.xml 파일을 연다.

Navigation Editor에서 New Destination 아이콘을 클릭한다.

Create new destination 버튼을 클릭한다.

New Android Fragment 창이 나타나면 Fragment (Blank)를 선택하고 Next 버튼을 클릭한다.

Fragment Name에 기본으로 기입되어있는 BlankFragment를 HomeFragment로 변경한다. Layout Name은 자동으로 변경된다.

Finish 버튼을 클릭하면 fragment destination 생성된다. 이때 프로젝트가 자동으로 싱크 된다.

 

 

Fig 7. 새로운 fragment destination 생성
Fig 8. 생성된 HomeFragment
Fig 9. navigation graph에 추가된 HomeFragment

 

➕ 이 과정으로 FavoriteFragmentHistoryFragment를 추가한다.

 

Fig 10. navigation graph에 추가된 HomeFragment, FavoriteFragment, HistoryFragment

 


6. destination의 속성들

아래의 navigation graph에는 앞에서 추가한 fragment destination들의 속성들이 있다. 또한 속성들은 Component tree에서 destination을 클릭했을 때 나오는 Attributes 창을 통해서도 확인할 수 있다. 이러한 destination 속성들에는 어떤 것들이 있는지 살펴보자.

<?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/my_nav_graph"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.abc.navtest.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" />
    <fragment
        android:id="@+id/favoriteFragment"
        android:name="com.abc.navtest.FavoriteFragment"
        android:label="fragment_favorite"
        tools:layout="@layout/fragment_favorite" />
    <fragment
        android:id="@+id/historyFragment"
        android:name="com.abc.navtest.HistoryFragment"
        android:label="fragment_history"
        tools:layout="@layout/fragment_history" />
</navigation>

 

Fig 11. destination attributes

 

 • type : destination이 fragment, activity 혹은 custom 중 어떤 것을 구현하고 있는지 나타낸다.

 • id : 코드상에서 이 destination을 나타내는 ID로 사용된다. 여기에서는 homeFragment가 기본으로 설정되어있다.

 • label : destination에 해당하는 xml layout 파일의 이름을 나타낸다. 여기에서는 HomeFragment의 레이아웃 리소스인 fragment_home.xml이 해당된다.

 • name : destination과 연관된 클래스의 이름을 나타낸다. 드롭다운으로 여러 개의 클래스중에서 하나를 선택할 수 있다. 드롭다운을 해보면 HomeFragment, FavoriteFragment와 HistoryFragment 가 나오는데 여기서는 HomeFragment를 선택한다. (Code에는 이미 name 속성에 값이 채워져 있다.)

 

start destination을 설정할 수 있는 app:startDestination 속성은 앱을 시작할 때 처음 보이는 화면을 설정한다. Navigation editor상에는 Fig 10에서 나와있는 것과 같이 집 모양 아이콘으로 이를 나타낸다.

<navigation 
...
    app:startDestination="@id/homeFragment">
...

destination을 start destination으로 선택하기 위해서는 우선 start destination으로 설정을 원하는 destination을 클릭하여 선택한다. 그리고 다음 2가지 방법 중에서 하나를 선택하여 따라 하면 된다.

방법 1 : Navigation editor 상단 툴에서 집 모양 아이콘(Assign start destination)을 클릭한다.

방법 2 : 마우스 우클릭하여 나오는 메뉴에서 'Set as Start Destination'을 선택한다.

 

Fig 12. start destination을 선택하는 두가지 방법

 


7. destination 연결 (action)

action의 특징을 간단히 살펴보면 다음과 같다.

 • destination들 사이를 논리적으로 연결한다.

 • navigation graph 상에서 화살표로 표시된다.

 • 일반적으로 하나의 destination에서 다른 destination으로 연결된다.

 • global action을 사용하면 앱의 어느 곳에든지 특정 destination으로 연결할 수 있다.

두 destination의 연결은 destination 위에 마우스를 올리면 오른쪽에 조그만 점 같은 원이 나타나는데 이 점을 드래그하여 연결을 원하는 destination 위로 이동하여 놓으면 화살표가 만들어지면서 이루어진다. 화살표를 선택하고 삭제를 하면 화살표가 사라지고 연결이 끊어진다.

 

Fig 13. destination 연결

 

이렇게 두 destination을 연결하면 연결을 시작한 destination에 <action> 요소가 추가된다. 이 요소의 속성을 보면 action의 id 속성(자신의 id)과 대상 destination의 id(대상의 id)를 나타내는 destination 속성으로 이루어져 있다. 또한 Navigation editor 화면에서 만들어진 화살표를 선택하면 우측 상단의 Attributes 화면에 type이 'action'으로 표시되는 것 또한 확인할 수 있다.

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.abc.navtest.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" >
        <action
            android:id="@+id/action_homeFragment_to_favoriteFragment2"
            app:destination="@id/favoriteFragment" />
    </fragment>

 

Fig 14. action 요서의 속성들

 


8. bottom navigation view

Navigation Component를 이용하면 Bottom navigation view와 쉽게 연결할 수 있다. 액티비티에 Bottom navigation view를 추가하고 navigation graph와 연결하는 과정을 살펴보자.

➕ Bottom navigation view에 나타날 icon 리소스들을 추가한다.

 

Fig 15. Bottom View에 들어갈 아이콘 리소스 추가

 

➕ 메뉴의 타이틀로 사용될 string들을 res/values/strings.xml에 추가한다.

    <string name="home">Home</string>
    <string name="favorite">Favorite</string>
    <string name="history">History</string>

Bottom navigation view에 들어갈 메뉴를 생성한다.

 

Fig 16. menu 생성

 

bottom_nav_menu.xml을 아래와 같이 구성한다. 여기서 중요한 부분은 Bottom navigation view와 fragment destination을 맵핑시켜야 한다는 것이다. 맵핑은 해당 메뉴 아이템의 id와 navigation graph의 destination의 id가 같도록만 해주면 간단히 된다. 즉, homeFragment, favoriteFragment, historyFragment 이 값들을 메뉴 아이템의 id 값으로 사용해야 한다.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/homeFragment"
        android:icon="@drawable/ic_home"
        android:title="@string/home" />
    <item
        android:id="@+id/favoriteFragment"
        android:icon="@drawable/ic_favorite"
        android:title="@string/favorite" />
    <item
        android:id="@+id/historyFragment"
        android:icon="@drawable/ic_history"
        android:title="@string/history" />
</menu>

➕ 위에서 생성한 메뉴를 포함하는 bottom navigation view를 액티비티에 추가한다.

<?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">


    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/my_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        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/my_nav_graph" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="@+id/my_nav_host_fragment"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/bottom_nav_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>

각 fragment에 다음 destination으로 이동할 버튼을 추가한다.

 

Fig 17. 버튼이 추가된 Fragment

 


9. destination 이동

앞에서 이미 메뉴 아이템의 id를 통해 fragment destination과 Bottom Navigation View의 아이템을 맵핑하였다. 이 때문에 Bottom Navigation 버튼을 통해서 fragment가 교체되는 것을 확인할 수 있다. 이번에는 각 fragment에 있는 버튼을 눌렀을 때 onClickListener과 action을 사용하여 destination들을 교체하는 방법을 살펴보자.

 

Fig 18. destination에 Action 추가

 

my_nav_graph.xml에서 action이 아래와 같이 구성될 수 있도록 한다. 각 action의 id가 동일한 'action_next'로 되어있는데 fragment 별로 구별되니 상관없다.

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
	...
    android:id="@+id/my_nav_graph"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
		...
        <action
            android:id="@+id/action_next"
            app:destination="@id/favoriteFragment" />
    </fragment>
    <fragment
        android:id="@+id/favoriteFragment"
		...
        <action
            android:id="@+id/action_next"
            app:destination="@id/historyFragment" />
    </fragment>
    <fragment
        android:id="@+id/historyFragment"
		...
        <action
            android:id="@+id/action_next"
            app:destination="@id/homeFragment" />
    </fragment>
</navigation>

 

여기까지 하면 버튼을 누를 때마다 화면이 해당 fragment destination으로 교체되는 것을 볼 수 있다. 구조가 복잡해 보이지만, 정리해보면 각 컴포넌트들이 아래와 같은 구조로 서로 연결되어있다.

 

Fig 19. 각 component들 사이의 개념적 연결

 


참고 사이트

Navigation component overview : developer.android.com/guide/navigation

Navigation getting started : developer.android.com/guide/navigation/navigation-getting-started

Jetpack Navigation Code Lab : developer.android.com/codelabs/android-navigation