Jetpack Navigation

강현성·2023년 8월 5일
0

android

목록 보기
17/18

1. Jetpack Navigation

Jetpack NavigationAndroid Jetpack의 일부로, 앱 내의 화면 간 이동을 간소화하고 표준화하는 데 사용되는 컴포넌트입니다. 사용자가 앱 내에서 화면 간 이동을 할 때의 과정을 효율적으로 관리해주며, 더 나은 사용자 경험과 개발자 경험이 가능하도록 합니다.
Jetpack Navigation 공식 문서

1-1. Jetpack Navigation 장점

  • 코드의 간결성과 유지보수성
    Jetpack Navigation은 화면 간의 이동 로직을 직관적인 방식으로 정의할 수 있게 해준다. 이로 인해 코드가 간결해지고 유지보수가 쉬워진다.

  • 안전한 인수 전달
    Safe Args 플러그인을 사용하면 프래그먼트 간에 데이터를 안전하게 전달할 수 있으며, 컴파일 시점에 오류를 찾아낼 수 있어서 런타임 오류를 방지할 수 있다.

2. FragmentManager

Jetpack Navigation이 나오기 전에는 FragmentManagerFragmentTransaction을 사용하여 프래그먼트 간 이동을 다음과 같이 구현했다.

  • activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
  • MainActivity
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 첫 번째 프래그먼트를 컨테이너에 추가
        if (savedInstanceState == null) {
            supportFragmentManager.beginTransaction()
                .add(R.id.fragment_container, FirstFragment())
                .commit()
        }
    }
}
  • FirstFragment
class FirstFragment : Fragment() {

    fun navigateToSecondFragment() {
        val secondFragment = SecondFragment()

        // 데이터를 전달하려면 Bundle 객체를 사용
        val bundle = Bundle()
        bundle.putString("key", "value")
        secondFragment.arguments = bundle

        // FragmentTransaction을 사용하여 SecondFragment로 이동
        parentFragmentManager.beginTransaction()
            .replace(R.id.fragment_container, secondFragment)
            .addToBackStack(null)
            .commit()
    }
}
  • SecondFragment
class SecondFragment : Fragment() {

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

        // FirstFragment에서 전달한 데이터를 받음
        val value = arguments?.getString("key")
    }
}

하지만, 위 코드처럼 FragmentManager로 프래그먼트 간 이동을 구현면 백스택 관리, 타입 안전성 등 개발자가 신경써야 할 부분이 많다.

하지만, 이러한 부분들을 Jetpack Naivgation을 사용하면 해결할 수 있다.

백스택 관리 같은 경우는 Jetpack Navigation이 개발자를 대신에 백스택을 관리해준다.

Jetpack Navigation 라이브러리를 사용하는 경우 FragmentManager와의 직접적인 상호작용은 거의 필요하지 않습니다. 개발자를 대신해 이 라이브러리가 FragmentManager를 사용하기 때문입니다.
FragmentManager 공식 문서

타입 안전성 같은 경우는 Safe Args를 통해 컴파일 과정에서 오류를 찾아내 런타임에 발생할 수 있는 타입 오류를 예방할 수 있다. 아래에서 확인할 것 이다.

3. 주요 구성 요소

3-1. NavHostFragment

NavHostFragment는 실제로 프래그먼트들이 교체되는 컨테이너 역할을 한다.

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

3-2. Navigation Graph

전체 앱의 네비게이션 흐름을 정의하는 XML 파일로서, 각 화면과 이 화면들 간의 이동 경로(action)를 선언한다.

<navigation xmlns:android="http://schemas.android.com/apk/res/android">
    <fragment android:id="@+id/firstFragment" android:name="com.example.FirstFragment">
        <action android:id="@+id/action_to_secondFragment"
                app:destination="@id/secondFragment"
                app:enterAnim="@android:anim/fade_in"
                app:exitAnim="@android:anim/fade_out" />
    </fragment>
    <fragment android:id="@+id/secondFragment" android:name="com.example.SecondFragment" />
</navigation>
  • <navigation>: 탐색 그래프의 루트, 탐색 그래프는 앱의 화면 간 이동 경로를 정의

  • <fragment>: 앱 내의 개별 화면(fragment)

    • 'android:name': fragment class name
  • <action>: 프래그먼트 간의 탐색 경로를 나타낸다. 예를 들어 첫 번째 프래그먼트에서 두 번째 프래그먼트로 이동하는 동작을 정의할 때 사용한다.

    • android:id: 액션의 고유 식별자로, NavContoller에서 navigate()의 인자값으로 들어가 코드 내에서 해당 액션을 호출할 수 있도록 한다.
    • app:destination: 액션이 가리키는 대상fragment의 ID 위 코드에서는 해당 액션을 호출하면 secondFragment로 이동한다.

3-3. NavController

NavGraph와 현재 상태를 연결하여 실제 이동을 처리한다. NavController를 통해 다른 프래그먼트로 이동하거나 이동 행동을 관리하는 탐색을 수행하는 컨트롤러이다. navigate()함수 인자값은 Navigation Graph에서 선언한 action의 id값이다.

findNavController().navigate(R.id.action_to_secondFragment)

3-4. Safe Args

Safe Args는 Android Jetpack Navigation 컴포넌트의 일부로, 프래그먼트 간에 데이터를 안전하게 전달할 수 있는 기능을 제공한다.

3-4-1. Safe Args 필요성

프래그먼트 간 데이터를 전달하려면 일반적으로 Bundle 객체를 사용하여 데이터를 전달한다. 하지만 이 방식을 타입 오류를 일으킬 가능성이 있으며, 잘못된 키 사용 등의 문제가 발생할 수 있다. Safe Args는 이러한 문제를 해결해준다.

즉, Safe Args는 타입 안전성을 제공한다. Safe Args는 각 프래그먼트 간에 전달되는 인수의 타입을 컴파일 시점에 검사한다. 잘못된 타입의 데이터가 전달되면 컴파일 오류가 발생하므로 런타임 오류를 방지할 수 있다.

예시

Safe Args를 사용하지 않는 경우 (잘못된 타입 사용하는 경우)

소스 프래그먼트에서 데이터 전달

val bundle = Bundle()
bundle.putInt("myKey", 42)
findNavController().navigate(R.id.secondFragment, bundle)

목적 프래그먼트에서 데이터 수신 (잘못된 타입 사용)

val myValue = arguments?.getString("myKey") // 잘못된 타입 사용

위 예시 코드는 런타임에서 에러를 발생시킨다. "myKey"에 연관된 값은 정수이지만, 문자열로 해석하려고 하고 있기 때문이다.

Safe Args를 사용하는 경우

Safe Args를 사용하면, 프래그먼트 간에 전달되는 인수의 타입을 컴파일 시점에 검사하므로 위와 같은 문제를 방지할 수 있다.

NavGraph에서 인수 정의

<fragment android:id="@+id/secondFragment" android:name="com.example.SecondFragment">
    <argument
        android:name="myIntArg"
        app:type="integer"
        android:defaultValue="0" />
</fragment>

소스 프래그먼트에서 인수 전달

val action = FirstFragmentDirections.actionToSecondFragment(42)
findNavController().navigate(action)

목적 프래그먼트에서 인수 수신

val args: SecondFragmentArgs by navArgs()
val myIntArg = args.myIntArg // 올바른 타입으로 수신

위 예시 코드는 인수의 타입이 일치하지 않으면 컴파일 오류가 발생하므로 런타임 오류를 방지하고, 코드의 안전성을 높일 수 있따.

3-4-2. Safe Args 사용 법

  • 프로젝트 수준 build.gradlde 설치
buildscript {
    repositories {
        google()
    }
    dependencies {
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0"
    }
}
  • 앱 수준 builde.gradle
apply plugin: "androidx.navigation.safeargs"

dependencies {
    implementation "androidx.navigation:navigation-fragment-ktx:2.6.0"
    implementation "androidx.navigation:navigation-ui-ktx:2.6.0"
}
  • NavGraph 내에 인수 정의
<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">

    <fragment android:id="@+id/firstFragment"
              android:name="com.example.FirstFragment">
        <action android:id="@+id/action_to_secondFragment"
                app:destination="@id/secondFragment">
            <argument
                android:name="user"
                app:argType="com.example.User" />
        </action>
    </fragment>

    <fragment android:id="@+id/secondFragment"
              android:name="com.example.SecondFragment" />

</navigation>
  • 데이터 전달: FirstFragment에서 SecondFragmentUser 인스턴스 전달
class FirstFragment : Fragment() {

    // 코드 생략

    fun navigateWithArgs(user: User) {
        val action = FirstFragmentDirections.actionToSecondFragment(user)
        findNavController().navigate(action)
    }
}
  • 데이터 수신: SecondFragment에서 인수를 받는다.
class SecondFragment : Fragment() {

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

        val args: SecondFragmentArgs by navArgs()
        val user = args.user

    }
}
profile
Hello!

0개의 댓글