본문 바로가기

Android

Navigation Component 사용하기

Navigation Component를 사용해서 화면 전환하는 것을 알아보자.

Navigation Component에 대해서 간단히 정리해보면

nav_graph.xml에 Navigation에 사용될 화면들을 정의해두고 Java에서 이것을 사용해서 화면전환을 하는 구조이다.

크게 Navigation을 실행하는 NavController가 있고,

화면이 전환되는 영역인 NavHostFragment가 있다.

화면 단위는 Destination이라는 용어로 부르고 Fragment를 사용해서 구성된다.

1. Destination 추가하기

오늘은 task_list에서 addEditTaskFragment로 화면전환을 할 것이다.
두 목적지 사이에 화살표는 Action이라고 불린다. 한 화면에서 다른 화면으로 이동할 수 있다는 것을 의미한다.

이제 NavHostFragment 영역을 선택된 Destination으로 전환하여 화면전환을 구현할 수 있다.

2. NavController 인스턴스 얻기

XML에 스크린 단위를 정의했으므로 이를 호출하여 실제 화면을 전환하는 NavController가 필요하다.

  • Navigation.findNavController(Activity, ViewId)
  • Navigation.findNavController(View)
  • View.findNavController(Fragment)
  • NavHostFragment.findNavController(Fragment)

위의 4가지 메소드를 상황에 맞게 호출하여 NavController 인스턴스를 얻을 수 있다.

3. navigate with Bundle

안드로이드 문서에서는 Navigation Component의 기능으르 SafeArgs 기능을 추천하고 있다. 화면 전환 간에 데이터를 첨부할 때 조금 더 쉽고 안전하게 데이터를 전달할 수 있게 도와주는 기능이다.

SafeArgs를 사용하면 별도의 라이브러리 추가가 필요하고 Direction이라는 새로운 Generated된 객체를 통해서 데이터를 옮기게 되는데
이번 프로젝트에서는 간단한 String, Boolean, Integer 값만 넘기면 충분하므로 기존의 Bundle을 사용해서 데이터를 전달한다.

옛날 방식과 같이 데이터를 첨부한 Bundle을 준비한다.
그리고 navController의 navigate(resId, bundle) 메소드를 통해 화면전환을 실행한다.

 

<NavContoller.navigate()의 구현코드>

이번에 사용하면서 깨달았는데 navController의 navigate 메소드의 구현을 보면
navOptions으로 popUpTo 그리고 PopUpToInclusive가 선택됐는지를 가장 상단에서 확인하고 있다.

popUpTo로 지정된 스크린이 나올때까지 스택에서 데이터를 팝하는 과정을 거친다.

 

그리고 Default Args를 첨부하여 새로운 NavDestination을 생성한다.


만약 [ FragmentA -> FragmentB ]로 이동 후  다시 [ FragmentB -> FragmentA ]로 가는 로직이 있다고 가정하자.

이 때 [ FragmentA -> FragmentB ]에서 다시 FragmentA로 돌아가는 방법은 2가지가 존재한다.

  • 뒤로가기 버튼
  • navigateToFragmentA()

이때 뒤로가기 버튼을 누를 경우 FragmentA의 onCreate()가 호출되지 않고 onResume()이 호출되는 것을 확인할 수 있다.
기존에 생성되어 스택에 저장해둔 FragmentA를 다시 꺼내는 것이다.

 

하지만 navigateToFragmentA()를 실행하면 FragmentA의 onCreate()부터 호출되는 것을 확인했다. 이는 Fragment를 새로 그리는 것이다.

 

 아래에서 설명할 popUpTo = fragmentA_id옵션의 경우 스택에서 FragmentA가 Pop할 때까지 계속 스택을 비우라는 명령어이다.
그리고 popUpToInclusive=true는 pop된 FragmentA까지 포함해서 Pop을 진행한다는 의미이다.

4. 실제 BackStack Log 찍어보기

<Without popUpTo option>

popUpTo를 사용하지않고 navigateToTaskListFragment() 했을때의 백스택을 출력해본 것이다.
과정을 반복할 수록 스택이 쌓인다. 스택에는 두 종류의 화면이 삭제되지 않고 계속 쌓여져간다.
(끝의 3자리 수가 788은 AddEditTaskFragment의 값이고, 969는 TaskListFragment의 값이다.)

 

 

<With popUpTo option, but not inclusive>

이번엔 popUpTo=TaskListFragment를 한 후에 실행결과이다. TaskListFragment를 만날 때까지 스택을 비우는 명령이므로
TaskListFragment 다음에 실행된 AddEditTaskFragment는 계속해서 삭제되고,

TaskListFragment(969)만 남아서 스택에 쌓여간다.

 

 

<With popUpTo option, and inclusive>

이번엔 popUpTo와 popUptoInclusive를 모두 사용한 로그이다.
TaskListFragment를 만날때까지 스택을 비운 후 TaskListFragment 본인도 포함해서 스택을 비우는 상태이다.

TaskListFragment < - > AddEditTaskListFragment를 왕복하는 가운데 추가로 스택이 쌓이지않고 하나의 화면끼리 주고 받는 상태가 완성되었다.

원래 구상은 처음 Home으로 시작되는 TaskListFragment는 스택에 있는 것을 재활용해서 onCreate() 없이 사용하고 싶었지만,
뒤로 가기 버튼을 눌렀을때 실행되는 로직과 달리 navigate메소드로 전환하는 경우 내부 스택에서 하나의 화면을 선택하는 선택권은 존재하지 않았다.

다만 일정 시점까지 스택을 비운 후에 새로 그 화면을 그려내는 방식이 최선이었다. 혹시 onCreate()를 거치지 않고 기존 프래그먼트를 스택에서 재활용해서 사용하고 업데이트 된 데이터는 ViewModel의 공유영역을 통해서 업데이트 될 수 있도록 고칠 수 있는 방법이 있나 생각해봐야할 것 같다.

 

Summary

간단하게 시행착오 과정을 기록해두려고 두서없이 쓰다가 보니 처음 Navigation Component를 접하는 사람에게 소개하는 글이 아니라 뒤죽박죽 난해한 글이 되버렸다.

 

정리하자면 navigate메소드 자체는 기존 스택에 존재하는 화면으로 이동하더라도 항상 new Instance()를 생성한다는 점이다.
(이게 당연한 로직이다. 새로운 Fragment에서 작업하고 뒤로가기 후 이전 스크린에 다시 접근할 수 있도록 하는 로직이 일반적이다.)

 

하지만 이번에 AddEditTaskFragemnt는 새로운 Task를 저장하고나면 뒤로가기를 눌렀을때 다시 접근해서는 안되는 스크린의 형태이다.

오직 Add 버튼이나 Edit 버튼을 누르는 액션으로만 접근가능해야된다.

 

그래서 popUpTo를 사용하면 다시 돌아올 수 없게 스택의 내용을 비워낼 수 있다.
이 로직은 기존 스택 내용을 재활용하는 것은 아니고, 스택을 비우고 새로 만들어서 삽입하는 형태이다.

 

라이브러리를 사용하는 이점은 navigation관련코드 정의를 xml 정의로 따로 관리할 수 있는 점이 가장 편리했다.
그리고 animation 추가도 매우 간편해졌다고 소개하고 있는데 핵심기능 구현이 끝나면 Animation도 추가해볼 예정이다.

 

이번에 공부하면서 검색해보니 아직까지 유저들의 버그리포트를 기반으로 열심히 수정해나가는 중이고 활발하게 버전이 업데이트되는 중이다.

 

Navigation Component는 기존 백스택화면 관리의 어려움을 라이브러리를 통해 정해진 방법으로 접근하게 하여 사용하기 간편하도록 제공하는 것을 추구하는 것으로 보인다. 하지만 다음 버전에서 조금 더 사용자의 요구에 맞게 스택을 들여다보고 조작할 수 있는 메소드가 제공되면 좋겠다.

 

*위 스크린샷에서 백스택 로그를 찍어보기 위해서도 기존의 getSupportFragmentManger() 메소드를 사용해야만 했다.