Android/JetPack

Jetpack - Navigation

봄석 2019. 5. 2. 22:46

Jetpack - Navigation

네비게이션은 앱의 목적지, 즉 사용자가 탐색 할 수있는 앱의 어느 곳에서나 발생합니다 . 이러한 대상은 작업을 통해 연결됩니다 .

네비게이션 그래프는 당신의 목적지와 행동을 모두 포함하는 리소스 파일입니다. 그래프는 앱의 모든 탐색 경로를 나타냅니다.

  1. 대상 은 앱의 다른 콘텐츠 영역입니다.
  2. 동작 은 사용자가 사용할 수있는 경로를 나타내는 대상 간의 논리적 연결입니다.

프로젝트에 탐색 그래프를 추가하려면 아래와같이 설정합니다.

  1. 프로젝트 창에서 res디렉토리를 마우스 오른쪽 버튼으로 클릭 하고 새로 만들기> Android Resource File을 선택합니다. 새 리소스 파일 대화 상자가 나타납니다.
  2. 파일 이름 필드에 "nav_graph"와 같은 이름을 입력합니다.
  3. 선택으로부터 리소스 종류 드롭 다운 목록에서 Navigation을 클릭 확인 .

 

  1. 대상 패널 : 현재 그래프 편집기 에있는 탐색 호스트와 모든 대상을 나열합니다 .
  2. 그래프 편집기 : 탐색 그래프의 시각적 표현을 포함합니다. 텍스트 보기 에서 디자인 보기와 기본 XML 표현 사이를 전환 할 수 있습니다 .
  3. 속성 : 탐색 그래프에서 현재 선택된 항목의 속성을 표시합니다.

 

내비게이션 구성 요소의 핵심 부분 중 하나는 NavigationHost입니다. NavigationHost는 사용자가 앱을 탐색 할 때 대상이 바뀌거나 바뀌는 빈 컨테이너입니다.

NavigationHost 는에서 파생되어야합니다 NavHost. 네비게이션 구성 요소의 기본 NavHost구현 인

 NavHostFragment,는 프레그먼트의  목적지에 대하여 처리합니다.

 

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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.appcompat.widget.Toolbar
        .../>

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"

        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        .../>

</android.support.constraint.ConstraintLayout>

NavHostFragment는 액티비티에 위와같이 추가합니다.

 

추가할때는 아래 사항에 유의해야 합니다 .

  • android:name속성이 필수입니다.
  • app:navGraph속성은 연관 NavHostFragment의 레이아웃 아이디를 기재합니다
  •  Navigation레이아웃  안에는  NavHostFragment는 사용자가 탐색 할 수있는 모든 목적지를 지정합니다 .
  •  app:defaultNavHost="true"속성은 NavHostFragment 시스템 뒤로 버튼을 가로 채도록합니다. 단 하나만 NavHost기본값이 될 수 있습니다. 동일한 레이아웃 (예 : 두 개의 창 레이아웃)에 여러 호스트가있는 경우 하나의 기본값 만 지정해야합니다.

네비게이션레이아웃 내부에 정의하기

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    app:startDestination="@id/blankFragment">
    <fragment
        android:id="@+id/blankFragment"
        android:name="com.example.cashdog.cashdog.BlankFragment"
        android:label="Blank"
        tools:layout="@layout/fragment_blank" />
</navigation>

네비게이션 내부의 레이아웃에 정의할때는 위와같이 정의하게됩니다.

id,name,label ,layout을정의합니다

 

 

 

액션으로 프래그먼트를 클릭하면 액션이 정의되게 됩니다. 화살표로 끌어 다른프래그먼트를 선택합니다.

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    app:startDestination="@id/blankFragment">
    <fragment
        android:id="@+id/blankFragment"
        android:name="com.example.cashdog.cashdog.BlankFragment"
        android:label="fragment_blank"
        tools:layout="@layout/fragment_blank" >
        <action
            android:id="@+id/action_blankFragment_to_blankFragment2"
            app:destination="@id/blankFragment2" />
    </fragment>
    <fragment
        android:id="@+id/blankFragment2"
        android:name="com.example.cashdog.cashdog.BlankFragment2"
        android:label="fragment_blank_fragment2"
        tools:layout="@layout/fragment_blank_fragment2" />
</navigation>

그렇게되면 위처럼 코드상에 액션이 표시되며 ,

이때 action의 id와 destination을 지정하게 됩니다.

 


 

위와같이 선언 후 목적지로 이동하려면 코드상에서 아래와같이 사용하게됩니다

 

Kotlin

 

네비게이션 및 백 스택

Android 는 방문한 목적지가 포함  백 스택  유지 관리합니다 . 

앱의 첫 번째 대상은 사용자가 앱을 열 때 스택에 배치됩니다. 

navigate() 메서드를 호출 할 때마다 스택의 맨 위에 다른 대상이 놓입니다. 

위로 또는 뒤로를 누르면 스택의 맨 위 대상 을 제거 (또는 팝) 하기 위해 NavController.navigateUp()  NavController.popBackStack()메서드가 각각 호출 됩니다

 

 Destinaion으로 의 이동은 NavController.navigate() 하나의 메소드로 이루어집니다.

findNavController() 메소드로 NavController를 찾고, navigate()는 Destination, Action 아이디를 파라미터로 받습니다.

 

 

popUpTo 및 popUpToInclusive

액션을 사용하여 탐색 할 때 선택적으로 추가 목적지를 백 스택에서 팝 할 수 있습니다. 예를 들어, 앱에 초기 로그인 흐름이있는 경우 사용자가 로그인하면 다시 버튼에서 사용자를 다시 로그인 흐름으로 가져 가지 않도록 모든 로그인 관련 대상을 다시 스택 밖으로 가져와야합니다.

특정 대상에서 다른 대상으로 이동할 때 대상 app:popUpTo을 표시하려면 연관된 <action>요소에 특성을 추가하면됩니다. app:popUpTo 호출의 일부로 백 스택에서 일부 대상을 팝하도록 탐색 라이브러리에 지시합니다 navigate(). 속성 값은 스택에 남아 있어야하는 가장 최근 대상의 ID입니다.

또한에 app:popUpToInclusive="true"지정된 대상 app:popUpTo이 백 스택에서도 제거되어야 함을 나타 내기 위해 포함시킬 수 있습니다 .

 

 

ex)popUpTo 순환 논리

앱에 A, B 및 C의 세 가지 대상이 있고 A에서 B, B에서 C, C에서 A로 이어지는 작업이 있다고 가정 해 봅시다. 해당 탐색 그래프는 그림 6과 같습니다.

각 네비게이션 동작에서 대상이 백 스택에 추가됩니다. 이 흐름을 반복적으로 탐색 할 경우 백 스택에는 각 대상 (A, B, C, A, B, C, A 등)의 여러 세트가 포함됩니다. 이 반복을 피하기 위해 다음 예와 같이 대상 C에서 대상 A로 이동하는 동작을 지정 app:popUpTo하고 지정할 수 있습니다 app:popUpToInclusive.

<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="true"/>
</fragment>

대상 C에 도달하면 백 스택에는 각 대상 (A, B, C)의 인스턴스가 하나씩 포함됩니다. 대상 A로 다시 탐색 할 때 A도 표시됩니다. popUpTo즉, 탐색하는 동안 스택에서 B와 C를 제거합니다. 함께 app:popUpToInclusive="true"스택 A의 첫 번째 A를 팝하여 효과적으로 지울 수 있습니다. 여기서는 사용하지 않을 경우 app:popUpToInclusive백 스택에 대상 A의 인스턴스가 두 개 포함될 것입니다.

 

 

 

액티비티의 역할

네비게이션 컴포넌트는 Activity의 역할을 기존과 다르게 바라볼 것을 요구합니다. 

원래 Activity는 화면의 Entry Point 이면서도 Content와 Navigation Method를 들고있는 Owner 였습니다.

 하지만 네비게이션 컴포넌트를 활용하기 위해서는 Entry Point로서의 역할만 보아야 합니다. 

Content와 Navigation Method는 모두 NavHost 라는 Fragment에게 위임합니다. 

그리고 이는 대부분의 화면을 Single Activity로 설계해야 함을 의미합니다.

 

데이터전달

프래그먼트끼리 데이터를 전달할때 보통 Argument Bundle을  사용하곤 합니다.

마찬가지로 navigation에서도 번들을통해 데이터를 전달할 수 있습니다.

 

findNavController().navigate(R.id.clubDetailFragment,Bundle().putObject(item))

위처럼 번들을 만들어 데이터를 설정할 수있습니다. 반대로 받는쪽에서는 아래와 같은 방법으로 받게됩니다.

arguments?.getObject()!!

 

 

SafeArgs

SafeArgs 기능은 Navigation Graph의 Arguments 정의에 따라 자동으로 Boilerplate Code를 생성하여 위 문제를 해결합니다. 보내는 쪽에서는 Directions Class가 생성되고, 받는 쪽에서는 Args Class가 생성됩니다. 그리고 각 클래스는 보내고 받아야 할 Arguments를 인스턴스 변수로 가지고 있습니다.

 

// Navigation Graph에서 Arguments를 정의
<fragment
    android:id="@+id/gameFragment"
    android:name="com.rfrost.navigationsample.GameFragment"
    android:label="GameFragment"
    tools:layout="@layout/game_fragment">
    <argument
        android:name="screenName"
        app:argType="string" />
    <argument
        android:name="userName"
        android:defaultValue="name"
        app:argType="string" />
</fragment>
// 보내는 쪽에서는 Directions 클래스를 활용
// 보낼 Arguments를 Constructor 및 Setter로 안전하게 설정
val directions = MainFragmentDirections.action("GameFragment")
directions.setUserName("rfrost")
view.findNavController().navigate(directions)
// 받는 쪽에서는 Args 클래스를 활용
// Bundle로부터 arguments를 받아오고, Instance 변수로 안전하게 접근
arguments?.let {
    val arguments = GameFragmentArgs.fromBundle(it)
    txt_screen_name.text = arguments.screenName
    txt_name.text = arguments.userName
}

 

 

Global Action

  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/mainFragment">


      
      ...
  
Global Action 사용

  findNavController().navigate(R.id.action_global_secondMain)


Fragment -> Activity -> Fragment 데이터 전송

  // 데이터를 보내는 fragment
  val message = activity_message.text.toString()
  val action = MainFragmentDirections.actionMainFragmentToSecondActivity(message)
  findNavController().navigate(action)
// 데이터를 받는 activity
  val args: NavGraphSecondArgs by navArgs()
  override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_second)
      setSupportActionBar(second_toolbar)

      val navController = findNavController(R.id.second_nav_host_fragment) 

      val bundle = bundleOf("message" to args.message)
      navController.setGraph(navController.graph,bundle)
  }

 

//데이터를 받는 새로운 activity의 fragment
  val args : SecondMainFragmentArgs by navArgs()
  override fun onCreateView(
      inflater: LayoutInflater, container: ViewGroup?,
      savedInstanceState: Bundle?
  ): View? {
      // Inflate the layout for this fragment
      return inflater.inflate(R.layout.fragment_second_main, container, false)
  }

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

      val receivedMessage = args.message
      message.text = receivedMessage
  }

 

 

참고링크 -https://developer.android.com/guide/navigation/navigation-global-action