본문 바로가기
Android/Navigation Component

[Android] Jetpack Navigation Component 딥 다이브

by 태크민 2023. 9. 7.

Navigation Component

Navigation Component는 안드로이드 JetPack 라이브러리 중 하나로 사용자의 상호작용에 따라 화면간의 이동을 구현하는데 도움을 준다.

이를 사용하면 개발자를 대신해 Navigation Component가 Fragment Manager를 내부적으로 사용하여 트랜잭션 관리, BackStack  처리 등을 대신해주기 때문에 쉽게 개발이 가능하다.

 

FragmentMent의 이해가 필요하다면 아래 링크를 참조하자.

https://jtm0609.tistory.com/237

 

[Android] Fragment Manager

Fragment 이동 처리 관련해서는 Jetpack의 Navigation Component를 쓰고 있지만, FragmentMananger의 동작 과정에 대해서 잘 이해하고 있지 못한 것 같아 포스팅을 해본다. FragmentManagerFragmentManager는 앱 프래그먼

jtm0609.tistory.com

 

 

Navigation은 FragmentManager를 이용해서 실제 내부에서 어떤 동작을 하고 있을까?

    public class FragmentNavigator extends Navigator<FragmentNavigator.Destination> {
    ..
        /**
         * {@inheritDoc}
         * <p>
         * This method should always call
         * {@link FragmentTransaction#setPrimaryNavigationFragment(Fragment)}
         * so that the Fragment associated with the new destination can be retrieved with
         * {@link FragmentManager#getPrimaryNavigationFragment()}.
         * <p>
         * Note that the default implementation commits the new Fragment
         * asynchronously, so the new Fragment is not instantly available
         * after this call completes.
        */
        @SuppressWarnings("deprecation") /* Using instantiateFragment for forward compatibility */
        @Nullable
        @Override
        public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
                @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
            ..

            final Fragment frag = instantiateFragment(mContext, mFragmentManager,
                    className, args);
            frag.setArguments(args);
            final FragmentTransaction ft = mFragmentManager.beginTransaction();

            ..

            ft.replace(mContainerId, frag);
            ft.setPrimaryNavigationFragment(frag);

            ..

            ft.setReorderingAllowed(true);
            ft.commit();

            ..
        }
    }

 

FragmentTransaction을 이용해 내부적으로 replace를 통해 화면 전환이 구현되어 있음을 알 수 있었고,

commitNow()가 아닌, commit()을 통해 비동기적으로 트랜잭션을 수행하고 있음을 확인할 수 있었다.


 

Navigation Component의 구성 요소

1. NavGraph(탐색 그래프)

  • Navigation과 관련해 모든 정보를 가지고 있는 구성 요소로, 어떤 목적지들이 있는지, 이동 중 어떤 액션을 취할 것인지, 어떤 데이터를 넘겨줄 것인지에 대한 정보가 담겨져 있다.
  • XML 리소스 파일

 

2. NavHost

navigation graph로부터 destination을 보여주는 빈 컨테이너이다. 즉, destination이 나타나고 사라지는 컨테이너에 해당된다.

 

우리가 Navigation을 사용하려면 xml에서 NavHost가 구현된 클래스인 NavHostFragment를 명시를 해줘야한다.

NavHostFragment를 살펴보면 아래와 같다.

 /**
  * Each NavHostFragment has a {@link NavController} that defines valid navigation within
  * the navigation host. This includes the {@link NavGraph navigation graph} as well as navigation
  * state such as current location and back stack that will be saved and restored along with the
  * NavHostFragment itself.
  *
  * NavHostFragments register their navigation controller at the root of their view subtree
  * such that any descendant can obtain the controller instance through the {@link Navigation}
  * helper class's methods such as {@link Navigation#findNavController(View)}. View event listener
  * implementations such as {@link android.view.View.OnClickListener} within navigation destination
  * fragments can use these helpers to navigate based on user interaction without creating a tight
  * coupling to the navigation host.
 **/
 
public class NavHostFragment extends Fragment implements NavHost {
..
}

NavHostFragment에는 내비게이션 호스트 내에서 유효한 내비게이션을 정의하는 NavController가 있다.(바로 아래에서 설명)

여기에는 내비게이션 그래프뿐만 아니라 NavHostFragment 자체와 함께 저장 및 복원될 현재 위치 및 백스택과 같은 내비게이션 상태도 포함된다.
NavHostFragments는 View 하위 트리의 루트에 내비게이션 컨트롤러를 등록하여 모든 하위 트리가 Navigation.findNavController(View)와 같은 Navigation Helper 클래스의 메서드를 통해 컨트롤러 인스턴스를 얻을 수 있도록 한다. Navigation Destination Fragment 내의 View.OnClickListener와 같은 이벤트 리스너 구현은 이러한 Helper를 사용하여 내비게이션 호스트에 대한 긴밀한 연결 없이 사용자 상호 작용을 기반으로 탐색할 수 있다.

 

3. NavController

선언된 action을 사용하기 위해서는 NavController가 필요하다. NavController NavHost 내에서 전반적인 탐색을 관리한다. 각 NavHost는 하나의 NavController를 가지고 있기에 직접 생성하거나 할 필요가 없이 아래의 메소드를 사용하여 획득할 수 있다.

  • Fragment.findNavController()
  • View.findNavController()
  • Activity.findNavController()

앱을 탐색할 때 navigation graph의 경로를 따라 탐색하거나 특별히 지정된 destination(화면)으로 탐색할 것임을 NavController에게 알리고, NavController는 NavHost에게 적절한 destination(화면)을 표시한다.

findNavController()

Fragment, View, Activity 등에서 사용하는 findNavController()는 NavController를 찾을 때 사용하는 확장함수입니다.

현재 NavController를 얻기 위한 확장함수로 아래와 같이 구현되어 있고, 결국 NavHostFragment의 findNavController를 이용해서 찾는다.

/**
 * Find a [NavController] given a [Fragment]
 *
 * Calling this on a Fragment that is not a [NavHostFragment] or within a [NavHostFragment]
 * will result in an [IllegalStateException]
 */
fun Fragment.findNavController(): NavController =
        NavHostFragment.findNavController(this)

 

NavHostFragment의 findNavController는 아래 코드와 같이 구성 되어 있으며, 특정 Fragmnet를 기준으로 해당 Fragment에 연결된 NavController를 찾아 반환한다.

public class NavHostFragment extends Fragment implements NavHost {
	/**
     * Find a {@link NavController} given a local {@link Fragment}.
     *
     * <p>This method will locate the {@link NavController} associated with this Fragment,
     * looking first for a {@link NavHostFragment} along the given Fragment's parent chain.
     * If a {@link NavController} is not found, this method will look for one along this
     * Fragment's {@link Fragment#getView() view hierarchy} as specified by
     * {@link Navigation#findNavController(View)}.</p>
     *
     * @param fragment the locally scoped Fragment for navigation
     * @return the locally scoped {@link NavController} for navigating from this {@link Fragment}
     * @throws IllegalStateException if the given Fragment does not correspond with a
     * {@link NavHost} or is not within a NavHost.
     */
    @NonNull
    public static NavController findNavController(@NonNull Fragment fragment) {
        Fragment findFragment = fragment;
        while (findFragment != null) {
            if (findFragment instanceof NavHostFragment) {
                return ((NavHostFragment) findFragment).getNavController();
            }
            Fragment primaryNavFragment = findFragment.getParentFragmentManager()
                    .getPrimaryNavigationFragment();
            if (primaryNavFragment instanceof NavHostFragment) {
                return ((NavHostFragment) primaryNavFragment).getNavController();
            }
            findFragment = findFragment.getParentFragment();
        }

        // Try looking for one associated with the view instead, if applicable
        View view = fragment.getView();
        if (view != null) {
            return Navigation.findNavController(view);
        }
        throw new IllegalStateException("Fragment " + fragment
                + " does not have a NavController set");
    }
 ..  
 }

 

먼저 특정 Fragment가 NavHostFragment인지 확인 후 아니라면 부모 체인을 따라가며 객체 비교를 통해 NavHostFragment가 맞는지 비교하며 찾게 된다.

만약 주어진 Fragment가 결국 NavController를 찾지 못하면 IllegalStateExcepntion을 발생시키게 된다.


 

Navigation Component의 장점

1. 프래그먼트나 화면간의 관계를 한눈에 볼 수 있음

부가적으로 이러한 장점은 개발자 간의 커뮤니케이션을 할 때도 도움이 된다.

 

2. 별도의 프래그먼트 트랜잭션 및 스택관리를 할 필요가 없음

UI 전환을위해 트랜잭션 관리와 애니메이션 처리 코드 작성이 가능하며,

프래그먼트 매니저로 스택 관리를 별도로 하지 않아서 구현하는 코드를 확연히 줄일 수 있다.

val navOptions = NavOptions.Builder()
       .setEnterAnim(R.anim.nav_default_enter_anim)
       .setExitAnim(R.anim.nav_default_exit_anim)
       .build()

findNavController().navigate(R.id.action_to_blank, navOptions)

 

3. 프래그먼트 간에 데이터를 전달하기 용이함

Bundle을 이용해 프래그먼트 간의 데이터를 전달할 때, 개발자가 직접 데이터의 key-value를 지정하게 되는데 데이터를 가져오는 과정에서 데이터의 이름이나 타입이 맞지 않을 경우 에러가 발생하거나 예상과 다른 상황이 생길 수 있다.

하지만, Navigation 컴포넌트는 Safe Args가 제공되어서 프래그먼트간에 전달할 사용할 데이터 타입을 미리 정의할 수 있다. 따라서 타입 안정성을 보장한다. 

binding.tvOne.setOnClickListener {
	val action = OneFragmentDirections.actionToResultFragment(age = 99)
    findNavController().navigate(action)
}
privatel val agrs: ResultFragmentArgs by navArgs()

override fun onViewCreated(view: View, saveInstanceState: Bundle?){
	super.onViewCreated(view, saveInstanceState)
    
   binding.tvName.text = agrs.age.toString()
}

만약 default value을 설정하지 않은 상태에서 공백 또는 다른 타입의 데이터를 보내려고 한다면 컴파일 에러가 발생하기 때문에 개발자가 더욱 안전하게 개발할 수 있다. (런타임 에러 방지)

 

4. Up, Back 버튼의 작업 등을 간단하게 자동으로 처리해줌

아래의 그림들을 예로 들면 첫번째 프래그먼트에서 두번쨰 프래그먼트로 이동하고 백 버튼을 눌렀을 때 다시 첫번째 프래그먼트로 이동하는 백버튼 처리를 Navigation에서 자동으로 구현해준다.

 


 

Navigation Component 예제

1. 의존성 설정

우선 Navigation Component를 사용하기 위해서 모듈 수준의 build.gradle 파일에 아래와 같이 작성하자.

build.gradle(Module)

dependencies {
  // 작성 시점 안정 버전
  def nav_version = "2.4.1"

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

Navigaion Component에는 Safe Args라는 플러그인이 있다. destination을 따라서 인자를 안전하게 전달하도록 도와주는 역할을 수행하는데 이를 사용하려면 아래와 같이 의존성을 추가로 설정해야 한다.

build.gradle(Project)

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

build.gradle(Module)

plugins {
  // for Java or mixed Java and Kotlin
  id 'androidx.navigation.safeargs'
  
  // only for Kotlin
  id 'androidx.navigation.safeargs.kotlin'
}

 

2, 프래그먼트 생성

예제에서 사용할 프래그먼트는 아래와 같다. 총 3개의 Fragment를 생성하였고 모두 같은 구조의 프래그먼트이다.

MainFragment.kt

class MainFragment: Fragment() {
    // DataBinding 사용
    private var _binding: FragmentMainBinding? = null
    private val binding: FragmentMainBinding
        get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = DataBindingUtil.inflate(
            layoutInflater, R.layout.fragment_main, container, false)

        return binding.root
    }

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

        binding.apply {

        }
    }
}

fragment_main.xml

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

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:id="@+id/btn_move"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="move"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

 

Navigation Host(NavHost) 컨테이너에 해당하고, 이를 통해 destination들이 나타나고 사라진다. Navigation Host는 반드시 NavHost로부터 파생되어야 한다. 기본적으로 NavHostFragment라는 클래스가 NavHost 인터페이스를 구현하고 있기에 이를 사용하면 된다. 아래는 activity의 XML 파일에 Navigation Host를 선언한 예제이다.

activity_main.xml

<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.fragment
        android:id="@+id/demo_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/navigation_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>

Fragment의 컨테이너 역할을 하는 fragment(FragmentContainerView도 가능)를 선언하고 Navigaion Host로 만들기 위해 여러가지 속성을 추가한다.

  • android:name : NavHost를 구현하는 클래스의 이름을 추가해주면 된다. 위에서 언급했듯이 NavHostFragment가 NavHost를 구현하고 있기에 해당 클래스의 전체 경로를 추가하였다.
  • app:navGraph : Navigaion graph 파일을 추가하면 된다. 아래에서 살펴보겠지만 navigaion graph는 XML 파일로 destination들을 선언하는 곳이다.
  • defaultNavHost  : 해당 속성을 true로 설정하면 back button을 눌렀을 때 이전 화면(destination)으로 전환된된다. 만약 false로 설정한다면 back button을 눌러도 이전 화면이 나타나지 않는다. 즉, 백스택에 destination들을 추가하는 속성이다. (System의 back button 이벤트를 intercept한다.)

 

navigation graph를 생성해보도록 하겠다. navigation graph는 모든 navigation 관련 자료를 포함하고 있는 XML 파일다. XML 파일이기 때문에 res 폴더안에 만들면 된다. 우선 폴더를 생성한 후 만들어보겠다.

new resource directory - resource type(navigation) 폴더 추가

폴더를 생성하였으면 폴더안에 resource type을 navigation으로 고른 후 XML 파일을 생성하면 된된다. 예제에서는 navigation_graph 라는 XML 파일을 생성하였다.

resource type(navigation) XML 파일 추가

 

처음 생성된 navigaion graph 파일은 위와 같이 생겼다. 아직 아무것도 추가되지 않은 상태인데, 만약 destination를 추가하고 싶다면 상단의 + 버튼(New Destination)을 클릭하면 된다.

  • Destination(목적지): 액티비티 또는 프래그먼트에 해당한다. 프래그먼트 같은 경우는 액티비티 위에 생성되기에 destination으로 넣을 때 아무런 조건이 붙지 않지만 액티비티를 destination으로 넣을 경우에는 조금 달라진다. 액티비티를 destination으로 넣는다는 것은 "현재 navigaion graph의 endpoint로 설정한다" 는 뜻이다. 즉, 현재 navigaion graph는 더 이상 사용하지 못하고 새로운 액티비티에 연결된 navigaion graph로 이동한다. 자세한 내용은 공식 사이트의 Create a new activity destination에 나와있다.

New Destination

New Destination을 선택하면 위와 같은 화면이 나타난다. 해당 기능을 통해 새로운 destination을 생성할 수도 있지만 위에서 이미 생성한 프래그먼트를 사용하도록 하겠다. 원하는 Fragment를 단지 클릭하면 자동으로 destination이 생성된다.

fragment 추가

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

    <fragment
        android:id="@+id/A"
        android:name="kr.co.lee.navigationcomponentexample.MainFragment"
        android:label="MainFragment" >
    </fragment>
    <fragment
        android:id="@+id/B"
        android:name="kr.co.lee.navigationcomponentexample.SecondFragment"
        android:label="SecondFragment" >
    </fragment>
    <fragment
        android:id="@+id/C"
        android:name="kr.co.lee.navigationcomponentexample.ThirdFragment"
        android:label="ThirdFragment" >
    </fragment>

</navigation>

생성했던 세 개의 프래그먼트를 destination으로 추가하였다. navaigion graph 파일의 루트 태그는 navigaion 이다. 해당 태그의 속성 중 app:startDestination 속성은 처음 보여질 화면으로 현재 A로 설정되어 있기에 id = A에 해당하는 MainFragment가 가장 먼저 보이게 된다. 그리고 navigaion 태그 안에는 위에서 추가했던 destination에 해당하는 세 개의 fragment 태그가 존재하는데 android:name 속성은 Fragment의 전체 경로에 해당한다.

이와 같이 선언하면 앱을 실행한다면 app:startDestination 으로 설정한 MainFragment만 보이게 된다. FragmentManager와 FragmentTransaction을 사용하지 않아도 Navigaion Component가 자동으로 관리하기 때문이다.

 

 

Action

Action은 destination 사이를 연결할 때 사용한다. Navigaion Graph에서 Action은 화살표로 나타나며, 한 destination에서 다른 destination을 가리키고 있는 모습이다. 하나의 destination을 선택한 후 오른쪽을 클릭하고 add action 탭으로 가면 아래와 같은 옵션이 있다.

  • To Destination : 현재 화면에서 다른 화면으로 이동하는 액션
  • To Self : 자기 자신으로 이동하는 액션
  • Return To Source : popBackStack시 사용될 액션
  • Global : 여러 destination에서 공통으로 사용할 수 있는 액션 (전역적인 액션)

다음은 하나의 action을 선언하고 선택한 화면이다.

Animations은 Action을 통해 destination을 들어가거나 나갈때 애니메이션을 설정하는 것이고 Pop Behavior은 백스택과 관련된 탭이다. 즉, 사용자가 뒤로가기 버튼으로 눌렀을 때 popUpTo라는 옵션에서 설정한 Fragment로 이동한다.

하지만, popUpToInClusive이라는 옵션에 따라 다르게 동작할 수 있는데, 아래에서 Pop Behavior의 popUpTo와 popUpToInClusive라는 속성에 대해서 알아보자.

  • popUpTo : 지정한 destination까지의 프래그먼트를 백스택에 제거한다.
  • popUpToInClusive : popUpTo로 지정한 destination 프래그먼트를 포함할지 말지를 설정하는 옵션이다. 속성은 false/true로 구분된다.
    • true -> 주어진 destination를 포함해서 모든 destination들을 백스택에서 제거한다.
      popupTo으로 설정한 Fragment로 이동한다.
    • false(또는 설정 하지 않은 경우) -> 지정된 destination을 제외한 모든 destination들을 제거한다. popUpto로 설정한 Fragment의 이전 Fragment로 이동한다.

예를 들어, 

 

예시 1.

위와 같은 내비게이션 그래프의 Flow가 존재할 때

<fragment
    android:id="@+id/c"
    android:name="com.example.myapplication.C"
    android:label="fragment_c"
    tools:layout="@layout/fragment_c">

    <action
        android:id="@+id/action_c_to_d"
        app:destination="@id/d"
        app:popUpTo="@+id/b"
        app:popUpToInclusive="false"/>
</fragment>

"C"에서 "D"로 가는 action에 popUpTo="@id/B"를 추가해준다면

"C"에서 "D"로 이동할 때 "C"는 사라지게 되고 "D"에서 뒤로가기를 한다면 "B"화면으로 이동하게 되는 것이다.

만일, popUpToInclusive를 true로 바꾸면,

"D"에서 뒤로가기를 하면 "A"로 이동되는 것을 확인할 수 있다.

 

예시 2.

위와 같이 C->D가 아닌 C->A로 가는 경우의 예시도 봐볼까요?

<fragment
    android:id="@+id/c"
    android:name="com.example.myapplication.C"
    android:label="fragment_c"
    tools:layout="@layout/fragment_c">

    <action
        android:id="@+id/action_c_to_a"
        app:destination="@id/a"
        app:popUpTo="@+id/a"
        app:popUpToInclusive="false"/>
</fragment>

 

popUpTo를 이번에는 a로 설정해주었다.

C->A로 이동을 할 때 B,C가 스택에서 제거가 되고 "A"만 남게 될 것이다.

만약 popUpToInclusive를 true로 한다면 모두 사라지게 될 것이다.

 

하나의 Action을 선언한 navigation_graph.xml

<fragment
    android:id="@+id/A"
    android:name="kr.co.lee.navigationcomponentexample.MainFragment"
    android:label="MainFragment" >
    <action
        android:id="@+id/action_A_to_B"
        app:destination="@id/B" />
</fragment>
<fragment
    android:id="@+id/B"
    android:name="kr.co.lee.navigationcomponentexample.SecondFragment"
    android:label="SecondFragment" >
</fragment>
<fragment
    android:id="@+id/C"
    android:name="kr.co.lee.navigationcomponentexample.ThirdFragment"
    android:label="ThirdFragment" >
</fragment>

action을 선언하면 XML 파일에는 위와 같이 생성이된다. framgnet라는 태그의 중첩 태그로 들어가고, 현재 A 프래그먼트에서 B 프래그먼트로 destination을 지정했기에 action 태그의 app:destination 속성으로 B가 지정되어 있다.

 

MainFragment에서 action_A_to_B 활성화

btnMove.setOnClickListener {
    // NavController 획득
    val navController = findNavController()
    // navigate는 현재 navigation graph의 destination을 탐색하는 메서드로
    // 여러가지 인자가 올 수 있다.
    navController.navigate(R.id.action_A_to_B)
}

 

Args, SafeArgs

마지막으로 Args를 사용하여 destination간에 데이터를 전달해보도록 하겠다. Args는 데이터를 전달받는 destination에 선언하는 것으로 navigaion graph 파일에 가서 destination을 선택하면 아래와 같이 Arguments 탭이 존재한다.

Arguments 탭에서 + 버튼을 클릭하면 아래와 같은 화면이 나타난다.

String, Int 등 타입뿐 아니라 Parcelable, Serializable, Enum 등도 지원한다. 예제에서는 String 타입의 SafeArgs를 선언하였다. Args를 생성하면 XML에는 다음과 같이 태그가 생성된다.

 

<fragment
    android:id="@+id/A"
    android:name="kr.co.lee.navigationcomponentexample.MainFragment"
    android:label="MainFragment">
    <action
        android:id="@+id/action_A_to_D"
        app:destination="@id/D" />
</fragment>
<fragment
    android:id="@+id/D"
    android:name="kr.co.lee.navigationcomponentexample.DataFragment"
    android:label="DataFragment">
    <argument
        android:name="message"
        android:defaultValue="No Message"
        app:argType="string" />
</fragment>

fragment 태그의 내부 태그로 argument라는 태그가 생성되었다. 

argType 이라는 속성은 해당 argument의 타입을 나타내고, defaultValue 라는 속성은 전달되는 argument가 없을 경우에 나타나는 데이터이다.

fragment a -> fragment d로 action이 연결되어 있기에 fragment a에서 fragment d로 보낼 데이터를 지정하면 된다.

 

이제 데이터를 보내고 받을 때 Safe Args를 사용해보도록 하겠다. Safe Args는 type에 안전한 데이터를 보장해주는 플러그인이다.

Safe Args를 활성화만해도 자동으로 각 action에 대해서는 클래스와 메서드를 생성해주고 각 destination에 대해서는 클래스를 생성해준다.

action에 대해서 생성되는 클래스의 이름은 만약 action의 id가 action_A_to_D라고 한다면 ActionAToD라는 클래스를 생성해주고, destination에 대해서 생성되는 클래스의 이름은 destination의 이름이 MainFragment라고 한다면 MainFragmentDirections라는 클래스를 생성해준다. 이를 이용해 MainFragment -> DataFragment로 데이터를 전달하는 코드는 아래와 같다.

데이터를 보내는 MainFragment.kt

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

    binding.apply {
        btnMove.setOnClickListener {
            // Action 생성
            // MainFragmentDirections은 MainFragment라는 destination에 자동으로 생성된 클래스
            // ActionAToD 클래스와 actionAToD 메서드는 action_a_to_d 라는 id를 가진 action에 대해 
            // 자동으로 생성됨
            val action: MainFragmentDirections.ActionAToD = MainFragmentDirections.actionAToD()

            // Action에 전달할 Argument의 name에 해당하는 변수가 자동으로 생성
            // DataFragement에 선언한 name이 message인 Argument
            action.message = etMessage.text.toString()

            // NavController 획득
            val navController = findNavController()
            // navController를 통해 action을 실행
            navController.navigate(action)
        }
    }
}

데이터를 받는 DataFragment.kt

override fun onStart() {
    super.onStart()
    
    arguments?.let {
        // DataFragmentArgs는 Args에 대해 자동으로 생성된 클래스
        val args = DataFragmentArgs.fromBundle(it)
        binding.tvMessage.text = args.message
    }
}

 

Safe Args를 사용하면 세가지 클래스가 자동으로 생성된다.

  1. Navigation의 action이 시작되는 대상의 이름에 Directions라는 접미어가 붙은 Directions 클래스
    ex) MainFragmentDirections
  2. 데이터를 수신하는 대상의 이름에 Args라는 접미어가 붙은  Args 클래스
    ex) DataFragmentArgs
  3. argumen를 전달하는 데 사용한 action의 이름과 동일한 이름의  Action 클래스
    ex) actionAToD()

 


참고자료

https://velog.io/@changhee09/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Navigation-Component

 

[안드로이드] Navigation Component

안드로이드 - Navigation Component

velog.io

 

https://thinkerodeng.tistory.com/290

 

[Kotlin/Android] Navigation (Pop behavior, navigateUp vs Back, ActionBar)

1. Pop behavior 이란 뒤로 이동시, popUpTo으로 설정한 fragment로 이동한다. 또한 fragment까지 백스택 내용을 제거한다. 1) popUpToInclusive : true - popUpTo으로 설정한 fragment까지 백스택에서 제거를 하고, popUpTo

thinkerodeng.tistory.com

 

https://amuru.tistory.com/310

 

[Android] popUpTo & popUpToInclusive

Jepack Navigation popUpTo & popUpToInclusive Android 개발 시 Jetpack Navigation을 사용해서 화면 이동을 구현하고, 백스택을 관리해야 하는 일이 많이 있다. 뒤로 가기를 했을 때 이전 화면이 아닌 원하는 화면으

amuru.tistory.com

https://velog.io/@simsubeen/Android-Kotlin-Jetpack-Navigation

 

[Android / Kotlin] Jetpack Navigation

기존에는 Fragment를 전환하려면 supportFragmentManager로 FragmentManager 인스턴스를 반환 받고 add나 replace 를 통해 Fragment를 교체하고 commit으로 직접 transaction을 실행했어야 했다. 그런데, Jetp

velog.io

https://brunch.co.kr/@oemilk/210

 

Android Navigation

Android Jetpack Navigation | 인앱 탐색에 필요한 모든 것을 처리합니다. Navigation은 앱 내의 화면 전환을 좀 더 쉽게 구현하고 화면 흐름을 시각화해서 볼 수 있도록 해주는 프레임워크입니다. 기존의 st

brunch.co.kr