Navigation을 알아보자!

Hanseul Lee·2022년 8월 31일
0

Jetpack을 공부하자!

목록 보기
2/5
post-thumbnail
post-custom-banner

Navigation은 무엇일까?

사용자는 앱을 다양한 콘텐츠를 접하기 위해 여러 화면을 왔다갔다 이동한다. 이때 Jetpack에서 제공하는 Navigation을 활용하면 단순하게 화면(Fragment) 전환을 구현할 수 있다. 뿐만 아니라 화면 간의 데이터 공유도 훨씬 쉽다!

왜 사용할까?

공식 문서는 다음과 같이 말한다.

  1. Fragment transaction을 편하게 다룬다
  2. Up, Back 작업을 올바르게 다룬다
  3. animation이나 trasaction 작업을 할 때 표준화된 리소스를 제공한다
  4. deep linking을 구현하고 다룬다
  5. 최소한의 작업으로 navigation drawers(앱 옆에 뜨는 거), Bottom Nav와 같은 navigation UI 패턴을 처리한다
  6. Safe Args를 활용한다 → 화면 간 데이터를 안전하게 제공하는 Gradle 플러그인
  7. ViewModel을 지원한다 → navigation graph로 범위를 지정해서 UI 데이터 공유가 가능하다

그러니까 Fragment 전환 시 아주 간단하고 데이터 공유도 쉽고 안전하니까 쓰라는 거 같다~

세 가지 컴포넌트(구성 요소)

아래 그림처럼 화면들이 어떻게 연결되어 있는지 시각적으로 보여주는 XML 리소스 파일. 그러니까 사용자가 앱에서 이동할 수 있는 경로를 그릴 수 있다.

navigation graph 내의 Fragment가 표시되는 빈 컨테이너.

navHost를 움직여서 화면을 보여줄 수 있게 하는 실질적인 존재. 리모컨처럼 보면 되겠다. 사용자는 NavController에게 Navigation Graph의 경로를 따라 움직이거나 특정 화면으로 가고 싶다고 말하고, 그럼 NavController는 사용자가 원한 화면, 그러니까 목적을 NavHost에 띄운다.

간단하게 만들어보자!

1. 환경 설정 (dependencies)

사실 둘이 별 차이는 없는데 아카이빙 목적으로 넣어둔다.

  • 공식문서(22.08.31) ver.

    // 프로젝트 build.gradle
    
    buildscript {
        repositories {
            google()
        }
        dependencies {
            val nav_version = "2.5.1"
            classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version")
        }
    }
    // 앱 build.gradle
    
    plugins {
        id("androidx.navigation.safeargs.kotlin")
    }
    
    dependencies {
      val nav_version = "2.5.1"
    
      // 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:$nav_version")
    }
  • goggle developer codelab ver.

    // 프로젝트 build.gradle
    
    buildscript {
        ext {
            nav_version = "2.3.1"
        }
        dependencies {
            classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
        }
    }
    // 모듈 build.gradle
    
    plugins {
        id 'org.jetbrains.kotlin.android'
        id 'androidx.navigation.safeargs.kotlin'
    }
    
    dependencies {
        implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
        implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
    }
  • plugins를 사용하는 버전

    // 프로젝트 build.gradle
    
    plugins {
    	id 'com.android.application' version '7.2.2' apply false
    	id 'com.android.library' version '7.2.2' apply false
    	id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
    	id 'androidx.navigation.safeargs.kotlin' version '2.5.1' apply false
    		}
    // 모듈 build.gradle
    
    plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id("kotlin-parcelize")
    id("androidx.navigation.safeargs.kotlin")
    		}
    
    dependencies {
        // navigation
    	def nav_version = "2.5.1"
    	implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
    	implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
    }

2. Navigation Editor (graph 만들기)

  1. 리소스 파일을 만들자.

    New Resource File을 활용해 만들고, 그러면 아래와 같은 XML이 만들어진다.

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

    처음엔 당연히 디자인 탭에도 아무것도 없다.

  2. Fragment를 추가하고 연결하자.

    위와 같이 추가를 하고,

    드래그를 해서 Fragment와 Fragment 간 연결하자.

    연결되면 위와 같이 확인할 수 있다.

    참고로 Fragment 이름 옆 집 아이콘은 시작점을 의미한다. Fragment를 클릭하고 위 아이콘을 눌러서 지정할 수 있다.

    여기까지 하고 나면 아래처럼 code가 바뀐 걸 알 수 있다!

    <?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/letterListFragment">
    
        <fragment
            android:id="@+id/letterListFragment"
            android:name="com.example.wordsapp.LetterListFragment"
            android:label="@string/app_name"
            tools:layout="@layout/fragment_letter_list">
            <action
                android:id="@+id/action_letterListFragment_to_wordListFragment2"
                app:destination="@id/wordListFragment" />
        </fragment>
        <fragment
            android:id="@+id/wordListFragment"
            android:name="com.example.wordsapp.WordListFragment"
            android:label="@string/word_list_fragment_label"
            tools:layout="@layout/fragment_word_list">
            <argument
                android:name="letter"
                app:argType="string" />
            <action
                android:id="@+id/action_wordListFragment_to_letterListFragment"
                app:destination="@id/letterListFragment" />
        </fragment>
    </navigation>

3. Activity에 NavHost 추가

XML에 다음과 같이 NavHostFragment를 추가해준다.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph"/>

</FrameLayout>
  • android:name → 구현 클래스의 이름
  • app:defaultNavHost="true" → 시스템이 뒤로가려고 할 때 NavHostFragment가 인터셉트한다. NavHost에 딱 하나가 기본값으로 지정되어야 한다.
  • app:navGraph → NavHostFragment와 Navigation Graph를 연결한다.

4. Activity에 NavController를 만들자

다음 코드는 위에서 만든 navHost를 id값 nav_host_fragment로 참조해 가져와 navController에 할당한다.

val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController

그리고 아래 코드를 이용하면 Fragment의 전환에 따라 액션바도 Fragment를 따라서 함께 움직이도록 세팅할 수 있다.

setupActionBarWithNavController(navController)

이름 그대로 액션바를 세팅하는 건데…무슨 말인지 이해가 어렵다면 캡쳐로 한 눈에 보자!

  • `setupActionBarWithNavController(navController)` 을 활용한 경우
  • 그렇지 않은 경우

아래 onSupportNavigateUp()을 활용하면 액션 바에서 뒤로가기 버튼을 누를 수 있게 된다.

override fun onSupportNavigateUp(): Boolean {
        return navController.navigateUp() || super.onSupportNavigateUp()
    }

전체 Acticity 코드는 아래와 같다.

class MainActivity : AppCompatActivity() {
    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val navHostFragment = supportFragmentManager
            .findFragmentById(R.id.nav_host_fragment) as NavHostFragment
        navController = navHostFragment.navController

        setupActionBarWithNavController(navController)

    }

    override fun onSupportNavigateUp(): Boolean {
        return navController.navigateUp() || super.onSupportNavigateUp()
    }

}

5. 안전한 데이터 전달을 위해 Safe Args를 사용하자

Safe Args는 NavDirections 객체를 반환하는 메서드로, SpecifyAmountFragmentDirecrions 클래스를 생성한다. 참고로 이 클래스의 이름은 자동으로 생성된다.

override fun onClick(view: View) {
    val action =
        SpecifyAmountFragmentDirections
            .actionSpecifyAmountFragmentToConfirmationFragment()
    view.findNavController().navigate(action)
}

이게 뭔 소리냐면, NavGraph의 Fragment를 따라 알아서 생성해준다는 말이다. 예를 들어 NavGraph에 LetterListFragment로 시작되는데, 그 Fragment는 아래와 같은 식으로!

// LetterListFragment 버튼의 text를 WordListFragment로 전달하는 예제

holder.button.setOnClickListener {
		val action = LetterListFragmentDirections.actionLetterListFragmentToWordListFragment(letter = holder.button.text.toString())
		holder.view.findNavController().navigate(action)
}

좀 더 정리해보면 이런 식으로 알아서 생성된다는 거다.

val action = {출발Fragment}Directions.action{출발Fragment}To{목적Fragment}()

추가 포스트에서 좀 더 자세하게 SafeArgs를 알아보자

팁) Label로 액션 바 내용 바꾸기

navGraph의 속성에서 label을 활용하면 액션바의 내용을 바꿀 수 있다. 이것과 Safe Args를 활용하면 데이터에 따라 유동적으로 변환도 가능한데 아직 이해가 제대로 되지 않아 나중에 추가 포스팅을 하겠다.

참고자료

개발자 문서”navigation“

개발자 문서 codelab “Fragments and the Navigation Component”

post-custom-banner

0개의 댓글