Android/Jetpack

Android Navigation Component 안드로이드 네비게이션 컴포넌트

SimSim 2021. 5. 27. 19:36

Android Jetpack에는 크게 4가지 카테고리가 있습니다. 그 중 아키텍처 카테고리에 속한 컴포넌트 중 하나인 네비게이션 컴포넌트 기본적인 내용을 학습하기 위해 새 프로젝트를 만들고 하나씩 설명하겠습니다.

 

 

Navigation Component 란?

네비게이션 컴포넌트는 API이자 앱 내에서 탐색 흐름을 기존보다 훨씬 쉽게 만들고 편집할 수 있게 해주는 안드로이드 스튜디오 디자인 도구입니다.

 

 

 

Navigation Component의 장점

* 프래그먼트 트랜잭션 처리.
* 기본적으로 '위로'와 '뒤로' 작업을 올바르게 처리.
-> AppBar를 사용하는 UI라면 '위로'와 '뒤로' 작업을 기본적으로 동일하게 처리합니다.
       백 스택이 없을 경우 '위로'는 앱을 종료하지 않습니다.
* 애니메이션과 전환에 표준화된 리소스 제공.
* 딥 링크 구현 및 처리.
* 최소한의 추가 작업으로 탐색 UI 패턴(예: 탐색 창, 하단 탐색) 포함.
* Safe Args - 대상 사이에서 데이터를 탐색하고 전달할 때 유형 안정성을 제공하는 그래프 플러그인입니다.
* ViewModel 지원 - 탐색 그래프에 대한 ViewModel을 확인해 그래프 대상 사이에 UI 관련 데이터를 공유합니다.
* Android Studio의 Navigation Editor를 사용하여 네비게이션 그래프를 통해 화면 전환 시나리오를 시각적으로 확인하기 쉽습니다.

 

 

먼저 제가 사용하고 있는 안드로이드 스튜디오 버전은 4.1.3 버전이고 언어는 Kotlin입니다.

 

 

새 프로젝트 만들기

Basic Activity Template를 선택해서 프로젝트를 만듭니다.

 

 

Dependency

    implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
    implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'

Module 레벨 build.gradle 파일에서 dependency 확인해보면 위에 있는 Navigation 디펜던시를 확인할 수 있습니다.

 

 

 

Safe Args 플러그인 추가

Safe Args 안정적인 매개변수 사용을 위해 쓰이는 플러그인 입니다. 기존에 쓰이던 Bundle을 바로 사용하는 대신 NavArgs를 이용해 키 값의 오타 또는 타입 미스 캐스팅으로부터 안전하게 argument를 사용할 수 있도록 도와줍니다.

 

Project 레벨의 build.gradle 파일

classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5" 추가합니다.

 

    dependencies {
        classpath 'com.android.tools.build:gradle:4.1.3'
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }


Module 레벨의 build.gradle 파일

상단에 id 'androidx.navigation.safeargs.kotlin'  추가합니다.

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'androidx.navigation.safeargs.kotlin'
}

 

 

네비게이션 구성요소

네비게이션 구성요소는 조화롭게 작동하는 세 가지 주요 부분으로 구성됩니다. 주요 부분은 다음과 같습니다.

  1. NavGraph(새로운 XML 리소스) - 하나의 중앙 위치에 모든 네비게이션 관련 정보가 포함된 리소스입니다. 여기에는 대상이라고 하는 앱의 모든 위치와 사용자가 앱에서 선택할 수 있는 가능한 경로가 포함됩니다.
  2. NavHostFragment(레이아웃 XML 뷰) - 레이아웃에 추가하는 특수 위젯입니다. 네비게이션 그래프와는 다른 대상을 표시합니다.
  3. NavController(Kotlin/JAVA 객체) - 네비게이션 그래프 내에서 현재 위치를 계속 추적하는 객체입니다. 네비게이션 그래프에서 이동할 때 NavHostFragment에서 대상 콘텐츠 전환을 조정합니다.

탐색 시 NavController 객체를 사용하여 이동하려는 위치나 네비게이션 그래프에서 선택하려는 경로를 알려줍니다. 그러면 NavController가 NavHostFragment에 적절한 대상을 표시합니다.

 

 

activity_main.xml 파일

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/Theme.Basic.AppBarOverlay">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/Theme.Basic.PopupOverlay" />

    </com.google.android.material.appbar.AppBarLayout>

    <include layout="@layout/content_main" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        app:srcCompat="@android:drawable/ic_dialog_email" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

 

앱이 시작되면 보여지는 메인 액티비티의 레이아웃 파일인 activity_main.xml을 살펴보면 content_main.xml 파일을 불러오는 것을 확인할 수 있습니다.

content_main.xml 파일을 열어 봅니다.

 

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <fragment
        android:id="@+id/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_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

<fragment> 태그의 속성을 살펴보면

 

   android:name="androidx.navigation.fragment.NavHostFragment"

   app:defaultNavHost="true"

-> 기본 NavHostFragment 로 지정합니다.


   app:navGraph="@navigation/nav_garden"

-> 사용할 navGraph 를 지정합니다.

 

이제 nav_graph.xml 파일로 이동해봅니다.

 

 

nav_graph.xml 파일

 

res/navigation 하위 폴더에 있는 nav_graph.xml 파일에는 해당 네비게이션 그래프가 모든 이동가능한 정보가 들어있습니다.

 

<navigation> 태그에 app:startDestination 속성은 처음 시작될 프래그먼트를 지정합니다.

 

<action> 태그에 있는 속성들은 크게 두 가지로 나뉩니다

1. 다음 fragment로 이동하고 이동했을 때 백 스택에 쌓여있는 다른 fragment들을 어떻게 처리할지에 관한 속성

(destination, popUpTo, popUpToInclusive, launchSingleTop)

2. 화면 전환시 애니매이션을 적용하는 속성

(enterAnim, exitAnim, popEnterAnim, popExitAnim)

 

app:destination 속성

- 대상 fragment로 이동합니다. 백스택에 put() 한다고 생각하시면 됩니다.

  --> 즉, destination 속성만 사용해서 a,b,a,b,a,b 이동하는 경우 인스턴스는 a,b 2개가 아니라 6개가 됩니다.

 

app:popUpTo 속성

- 현재 작업에서 다시 튀어나올 때까지 현재 fragment의 팝핑 옵션을 나타내는 데 사용됩니다.

  이 경우 이 목적지를 찾을 때까지 백 스택에서 일치하지 않는 목적지가 팝 됩니다.

  --> 이게 무슨 소리냐면 백스택의 경우 스택 자료구조로 되어있는데 a,b,c 순서대로 쌓여 있는 상태에서

a로 popUpTo 할 경우 b와 c는 Stack.pop() 되어서 a만 남는 방식의 동작입니다.

 

app:popUpToInclusive 속성

- 현재 인스턴스를 포함할지 여부를 지정하는 것입니다

 

app:LunchSingleTop 속성

- 네비게이션 액션이 싱글톤으로 시작되어야 하는지 여부를 사용합니다(즉, 백 스택 상단에 주어진 목적지의 사본이 기껏해야 한 권 있을 것이다). 이것은 안드로이드.content.Intent.FLAG_ACTIVITY_SINGLE_TOP이 활동과 함께 작동하는 방식과 유사하게 기능합니다.

 

참고(https://developer.android.com/guide/navigation/navigation-navigate?hl=ko#pop)

app : popUpToInclusive를 사용하지 않으면 fragment 간 자주 이동하면서 백 스택에는 특정 목적지의 두 개 이상의 인스턴스가 포함되어 있습니다.

 

 

 

다른 fragment로 이동하기

    <fragment
        android:id="@+id/FirstFragment"
        android:name="com.practice.jetpack.basic.FirstFragment"
        android:label="@string/first_fragment_label"
        tools:layout="@layout/fragment_first">

        <action
            android:id="@+id/action_FirstFragment_to_SecondFragment"
            app:destination="@id/SecondFragment" />
    </fragment>

 

먼저 nav_graph.xml 파일에 first fragment <action> 태그 부분을 살펴보면

app:destination="@id/SecondFragment" 속성을 이용해 이동하려고 하는걸 알 수 있습니다.

 

FirstFragment.kt 파일에 코드를 보면

        view.findViewById<Button>(R.id.button_first).setOnClickListener {
            findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
        }

findNavController().navigate(액션_태그_id) 로 지정해서 액션 태그에 정의된 속성대로 화면 전환을 합니다.

 

 

 

 

Safe Args 이용한 프래그먼트에 인자값을 전달

처음 프로젝트 생성한 후 추가해줬던 Safe Args 플러그인을 이용해서 Fragment간 인자값 전달하는 방법을 알아보겠습니다.

 

FirstFragment 에서 SecondFragment 로 값을 전달할 겁니다.

 

 

SecondFragment 에서 받을 인자 값 설정하기

 

nav_graph.xml 파일을 열어서 SecondFragment를 선택합니다.

오른쪽 Atrributes 탭에 있는 Arguments 부분의 + 를 누릅니다.

 

 

 

받을 인자값의 이름, 자료형, 기본값을 정한 후 Add 버튼을 누릅니다.

 

    <fragment
        android:id="@+id/SecondFragment"
        android:name="com.practice.jetpack.basic.SecondFragment"
        android:label="@string/second_fragment_label"
        tools:layout="@layout/fragment_second">

        <action
            android:id="@+id/action_SecondFragment_to_FirstFragment"
            app:destination="@id/FirstFragment" />
        <argument
            android:name="my_lucky_number"
            app:argType="integer"
            android:defaultValue="0" />

    </fragment>
    

 

이렇게 <argument> 태그 코드가 추가된 것을 확인할 수 있습니다.

 

안드로이드 스튜디오 상단 메뉴에 Build>Rebuild Proejct 를 누릅니다.

빌드가 끝나면 Safe Args 플러그인으로 인해 자동 생성된 클래스들을 확인할 수 있습니다.

 

 

 

인자값 넘겨주기

 

FirstFragment.kt 파일을 엽니다.

/**
 * A simple [Fragment] subclass as the default destination in the navigation.
 */
class FirstFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_first, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        view.findViewById<Button>(R.id.button_first).setOnClickListener {
            val actionFirstFragmentToSecondFragment =
                FirstFragmentDirections.actionFirstFragmentToSecondFragment(777)
            findNavController().navigate(actionFirstFragmentToSecondFragment)
        }
    }
}

 

버튼 클릭시 SecondFragment로 이동하는 자동생성된 FirstFragmentDirections 클래스를 이용해 인자값을 넣어주도록 수정해줍니다.

 

 

인자값 받기

 

SecondFragment.kt 파일을 엽니다.

/**
 * A simple [Fragment] subclass as the second destination in the navigation.
 */
class SecondFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_second, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val safeArgs: SecondFragmentArgs by navArgs()
        val luckyNumber = safeArgs.myLuckyNumber
        Toast.makeText(context, "luckyNumber = $luckyNumber", Toast.LENGTH_SHORT)
            .show()

        view.findViewById<Button>(R.id.button_second).setOnClickListener {
            findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment)
        }
    }
}

 

자동생성된 SecondFragmentArgs 클래스를 이용해서 FirstFragment로부터 전달된 인자값을 받습니다.

 

val safeArgs: SecondFragmentArgs by navArgs()
val luckyNumber = safeArgs.myLuckyNumber

by navArgs() 위임된 프로퍼티를 사용합니다.

이처럼 Safe Args 플러그인으로 인해 자동생성된 클래스에서 명확한 타입과 이름을 사용하기 때문에 안전하고 명확하게 인자값을 주고 받을 수 있습니다.

 

 

 

지원되는 인수 유형

 

 

이상 기본적인 네비게이션 사용방법을 알아보았습니다~!