[Android] Navigation

 

안드로이드를 다시 공부하고 있다. 가장 좋은 리소스는 구글에서 제공하는 것들 이라고 생각이 되어 구글 코드랩들을 하나씩 공부해 보려고 한다. 이번 포스팅에서는 안드로이드 Navigation에 대해 공부해 볼 것이다.

 

 

Jetpack Navigation

Right now you have this awesome navigation graph, but you're not actually using it to navigate. The Navigation component follows the guidance outlined in the Principles of Navigation. The Principles of Navigation recommend you use activities as entry point

codelabs.developers.google.com

 

Navigation은 3가지 주요 부분으로 구성 되어 있다.

  • NavGraph
  • NavHost
  • NavController

먼저 NavGraph에 대해서 알아보도록 하겠다.

 

NavGraph

 

모든 Navigation 정보가 모여있는 XML 리소스 파일. 여기에서 Destination이라고 부르는 앱 내의 모든 개별적 콘텐츠 영역과 사용자가 앱에서 갈 수 있는 모든 경로가 포함되어 있다.

 

NavGraph는 아래와 같은 모습으로 앱안에서 어떻게 Navigation이 이루어져야 하는지 보여준다.

 

 

XML은 아래와 같이 구성되어 있다. <navigation>은 모든 navagation graph의 root node이다. <navigation> 태그 안에 많은 destination들을 추가 할 수 있다. destination에는 <activity>, <fragment>가 있다.

 

app:startDestination 속성은 사용자가 처음 앱을 시작 했을 때, 보여져야 하는 default destination을 설정 할 때 사용한다.

<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"
    app:startDestination="@+id/home_dest">

    <!-- ...tags for fragments and activities here -->

</navigation>

 

그러면 <navigation> 태그안에 destination은 어떻게 정의 할까? 아래와 같은 코드를 살펴 보자. android:id는 외부에서 이 destination에 접근 하기 위해 고유한 ID를 설정 해 준다. android:name에는 해당 destination의 full class 경로를 넣어 준다. 이는 해당 destination으로 navigation 되었을 때, 이를 instantiate하기 위해 사용된다. tool:layout은 navigation graph를 design 모드로 봤을 때, 위의 사진 처럼 layout을 나오게 하는데 사용된다.

 

<fragment> 태그안에 <action>, <argument>, <deepLink>와 같은 태그들을 포함 시킬 수 있다.

 

<fragment
    android:id="@+id/flow_step_one_dest"
    android:name="com.example.android.codelabs.navigation.FlowStepFragment"
    tools:layout="@layout/flow_step_one_fragment">
    <argument
        .../>

    <action
        android:id="@+id/next_action"
        app:destination="@+id/flow_step_two_dest">
    </action>
</fragment>

 

NavGraph에 Destination 추가 하기

 

NavGraph에 Destination을 추가 하는 방법은 간단하다. design모드에서 아래와 같은 버튼을 누르면, 추가 할 수 있는 Destination 목록이 나온다. 여기서 원하는 Destination을 추가 해 주면 된다.

 

 

NavHost

 

구글에서 현재 추천하는 Navigation 방식은 액티비티는 앱의 entry point로만 사용하는 것이다. 이때, 액티비티는 BottomNavigation이나 Drawer Navigation 같은 global Navigation을 가질 수 있다. 구글이 추천하는 바는 단일 액티비티가 있고, Fragment들 사이에서 Navigation이 이루어져야 한다는 것 이다.

 

위와 같은 방식으로 구현하기 위해서는 액티비티가 NavHostFragment라는 특별한 widget을 가지고 있어야 한다. NavHostFragment는 NavGraph에 정의 된 대로 Fragment를 Navigation 시켜 주며, 이 때 각 desination들을 보여주는 역할을 한다. NavHostFragment는 하나의 그릇이고, 우리가 정의한 NavGraph에 따라 Fragment들이 담겨져서 보여진다고 보면 된다.

 

 

액티비티 속 NavHostFragment는 아래와 같이 정의 된다. 필수로 android:name에 NavHostFragment를 지정 해 줌으로써 해당 fragment가 NavHostFragment라는 것을 명시 한다. app:defaultNavHost="true" 속성은 해당 NavHostFragment를 System의 back 버튼과 연결 시키는 역할을 한다. defaultNavHost = "true" 속성은 단 하나만 지정되어야 한다. app:navgraph 속성에는 정의한 navigation graph xml파일을 등록 시켜 준다.

 

<LinearLayout
    .../>
    <androidx.appcompat.widget.Toolbar
        .../>
    <fragment
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/my_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/mobile_navigation"
        app:defaultNavHost="true"
        />
    <com.google.android.material.bottomnavigation.BottomNavigationView
        .../>
</LinearLayout>

 

NavController

 

NavHostFragment를 만드는 것 까지 했다. 그러면 버튼을 눌렀을 때, bottom Navigation의 item들을 클릭 했을 때, 화면전환이 이루어지게 하려면 어떻게 해야 할까? 이 때 필요한 것이 NavController다. Trigger가 발생했을 때, 화면전환을 시켜주는 역할을 한다. NavController는 아래와 같이 이용이 가능하다.

// Command to navigate to flow_step_one_dest
findNavController().navigate(R.id.flow_step_one_dest)

 

findNavController()라는 함수를 통해 NavController를 찾을 수 있고, navigation 함수를 호출하여 인자로 보낸 Destination 혹은 Action id값에 따라 적절한 Navigation을 수행한다. NavControler의 navigate()는 이전에 우리가 사용했던 Fragment의 replace나 StartActivity()와 같은 역할을 한다. 

 

NavOption

 

위에서 Navigation에 필수적인 3가지 요소 NavGraph, NavHost, NavController에 대해 알아봤다. 이번에는 navigate()가 수행될 때, 조금 더 유연하게 보이도록 애니메이션을 추가하는 방법에 대해 알아 보겠다. 이는 NavOption을 이용하면 된다. navOption을 정의하고 navigate()에 인자로 넘겨주면 된다.

 

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
    }
}
view.findViewById<Button>(R.id.navigate_destination_button)?.setOnClickListener {
    findNavController().navigate(R.id.flow_step_one_dest, null, options)
}

 

 

Action

 

Navigation()의 인자로 destination 뿐만 아니라 action도 전달이 가능하다. Action을 이용한 Navigation은 아래와 같은 장점이 있다.

  • NavGraph에서 Action은 시각적으로 app의 path를 보여준다.
  • Action은 transition animation이나 argument, backstack behavior같은 속성들을 가질 수 있다.
  • safe args 플러그인을 사용할 경우 navigate할 때 코드가 상당히 짧아지는 것을 볼 수 있다. 

 

 

위의 사진 처럼 Action이 적용되어 있다면 xml코드는 아래와 같이 작성되어 있을 것이다. action은 destination에 감싸져 있고, 해당 destination으로 부터 action이 가지고 있는 destination으로 navigation이 진행 된다.

 

<fragment
    android:id="@+id/flow_step_one_dest"
    android:name="com.example.android.codelabs.navigation.FlowStepFragment">

    <argument
        .../>

    <action
        android:id="@+id/next_action"
        app:destination="@+id/flow_step_two_dest">
    </action>
</fragment>

<fragment
    android:id="@+id/flow_step_two_dest"
    android:name="com.example.android.codelabs.navigation.FlowStepFragment">
    <!-- ...removed for simplicity-->
</fragment>

 

아래와 같은 사진도 한번 살펴 보자. flow_step_two_dest에서 home_dest로 action이 정의되어 있다.

 

이번에 action은 위 처럼 destination 속성을 가지지 않고, popUpTo 속성을 가지고 있다. popUpTo속성은 backstack에서 home_dest 도착할 때까지 item들을 pop하는 속성이다. Step Two가 최상단 화면일 때, 현재 backstack은 [StepTwo, StepOne, Home] 으로 되어 있을 것이다. popUpTo: app:popUpTo="@id/home_dest는 Home으로 돌아갈 때, 그 중간에 있는 StepOne 또한 pop을 시키겠다는 의미이다.

 

<fragment
    android:id="@+id/home_dest"
    android:name="com.example.android.codelabs.navigation.HomeFragment"
    .../>

<fragment
    android:id="@+id/flow_step_two_dest"
    android:name="com.example.android.codelabs.navigation.FlowStepFragment">

    <argument
        .../>

    <action
        android:id="@+id/next_action"
        app:popUpTo="@id/home_dest">
    </action>
</fragment>

 

Safe Args

 

Navigation Component는 safe args라는 그래들 플러그인을 가지고 있다. Sage Args를 사용하기 위해서는 최상위 프로젝트단 build.gradle에 classpath를 포함시켜야 한다.

 buildscript {
        repositories {
            google()
        }
        dependencies {
            def nav_version = "2.1.0"
            classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
        }
    }
    

 

또한 플러그인을 app단의 build.gradle에 적용해야 한다.

 

apply plugin: 'androidx.navigation.safeargs.kotlin'

 

이제 SafeArgs를 사용해서 navigation간의 데이터 전달을 해보자.

 

<fragment
    android:id="@+id/flow_step_one_dest"
    android:name="com.example.android.codelabs.navigation.FlowStepFragment"
    tools:layout="@layout/flow_step_one_fragment">
    <argument
        android:name="flowStepNumber"
        app:argType="integer"
        android:defaultValue="1"/>

    <action...>
    </action>
</fragment>

 

<argument> 태그를 사용하여 SafeArgs를 사용할 수 있으며, 이렇게 태그를 지정하면 FlowFragmentArgs라는 클래스가 자동적으로 생성 된다. XML에서 "flowStepNumber"라는 argument를 가지고 있기 때문에 FlowStepFragmentArgs에도 flowStepNumber라는 변수가 자동으로 정의되어 있다.

 

 

아래와 같이 argument를 얻어서 사용 할 수 있다.

val safeArgs: FlowStepFragmentArgs by navArgs()
val flowStepNumber = safeArgs.flowStepNumber

 

위의 사진에서 Directions라고 생성된 클래스들을 볼 수 있는데, 이는 action이 정의되어 있는 destination들은 자동적으로 Directions 클래스들이 생성 된다. Direction 클래스에서는 Navigation Graph에서 정의한 action들이 method형태로 정의되어 있다.

 

view.findViewById<Button>(R.id.navigate_action_button)?.setOnClickListener {
    val flowStepNumberArg = 1
    val action = HomeFragmentDirections.nextAction(flowStepNumberArg)
    findNavController().navigate(action)
}

댓글



Designed by JB FACTORY