”'Android Navigation' 테스트 후기 (Posted on Nov 26, 2020)
정상은제품길드 H파티 클라이언트 엔지니어
최근 Android Developers에 MAD Skills(Modern Android Development)이라는 섹션이 추가되었습니다. “미친 기술”이라니!!! 왠지 개발 효율을 확 끌어올려 줄 수 있는 무언가가 있을 것만 같아 저도 모르게 클릭을 하게 되었습니다. 대략 읽어보니 Kotlin, Jetpack 등을 사용하여 안드로이드 개발을 더 쉽고 빠르게 할 수 있는 최신 기술들을 소개해 주고 있는 것 같았고 그중 가장 첫 번째 시리즈가 바로 Navigation이었습니다.
Navigation Component는 꽤 오래전에 추가가 되었습니다. 2018년쯤에 공개가 되었고 Android Studio 3.6부터 Template 프로젝트에도 추가되었습니다. 그동안 Fragment 전환을 위해 해야 했던 수많은 반복 작업들… 코드로 UI를 직접 핸들링하고 애니메이션 처리도 해야 하고 Stack 관리도 직접 했던 수고스러움을 모두 해결해 준다고 하니 빨리 우리 서비스에 적용해 보고 싶었습니다.
하지만 결론부터 말씀드리면, 저희 서비스에 Navigation은 아직 적용이 어렵다고 판단하였습니다. 그동안 가이드 문서만 읽고 지나갔었던 Navigation Component를 실제로 연습해 보면서 느꼈던 점들을 몇 가지 적어보겠습니다.
문서에도 나와 있듯이 Navigation은 하나의 Activity 안에서 여러 Fragment 간의 전환에 중점을 두고 설계가 되었습니다. Activity 기반으로 설계된 저희 서비스 전체 Flow를 하나의 Navigation graph에 표현하는 것은 어려운 일이었습니다. 그렇다면 각 Activity마다 NavHostFragment를 두고 Navigation Graph를 만들어서 관리하면 되지 않을까라는 생각이 드실 거라 생각됩니다. 하지만 로그인 시나리오를 제외하고 각각의 기능에서 2depth, 3depth 이상 발생되는 시나리오가 없어 억지로 Navigation을 적용할 필요는 없다고 생각되었습니다.
하지만 서비스에 적용을 하기 위해 연습을 하면서 느꼈던 장점들은 분명히 존재하였습니다.
Good Point
Navigation에서 가장 눈에 띄는 바로 그것! Navigation Editor는 실제로 Fragment 간의 연관 관계를 한눈에 알아볼 수 있게 시각화해주어 아주 편리하다고 느꼈습니다. (아직 버그가 조금 있는 것 같긴 합니다만) 서비스의 시나리오를 쉽게 파악할 수 있고 개발자 간 커뮤니케이션에도 많은 도움이 될 것 같습니다.

시나리오에 필요한 Fragment와 Action을 추가해 주고 코드에서 NavController로 navigate만 호출해 주면 별도의 스택 관리 없이도 전환이 잘 이루어졌습니다. Deeplink를 통해 2depth 이상의 Fragment에 바로 접근 후 Navigate up 또는 Back pressed 동작을 하여도 back stack 시나리오가 그대로 동작하여 이 부분도 굉장히 편리하였습니다.
다음으로 Safe Args가 제공된다는 점입니다. Fragment에서 사용할 Arguments를 미리 정의해 두면 굳이 코드를 찾아볼 필요도 없고 별도의 타입캐스팅 없이 편리하게 사용할 수 있는 점이 좋았습니다.

Navigation Editor에 Argument를 입력하고 build를 하면 NavArgs를 상속받는 XXXArgs 가 generate 되게 됩니다. 여기에서 parameter에 직접 접근하여 사용할 수 있어 불필요한 코드가 사라지게 됩니다.
data class ConfirmDialogFragmentArgs( val message: String ) : NavArgs { fun toBundle(): Bundle { val result = Bundle() result.putString("message", this.message) return result } ... }
마지막으로 Deeplink 기능을 제공한다는 것입니다. 이 또한 Editor에서 직접 추가가 가능하기 때문에 Fragment 별로 Deeplink를 설정할 수 있고 Deeplink Uri의 path, query를 이용하여 arguments 또한 쉽게 전달할 수 있습니다.
하지만 장점만큼이나 아쉬운 점도 많이 있었습니다.
Bad Point
우선 Activity 간 전환 또는 Navigation Graph 간의 전환이 매끄럽지 못하다는 것입니다. 설계 자체가 하나의 Activity에서 여러 Fragment들을 전환하는 것에 초점이 맞춰져 있기 때문에 어쩔 수 없다고 생각하지만 Navigation Flow에서 다음 Activity로 넘어갈 때는 여전히 코드로 핸들링을 하여야 한다는 점이 아쉬웠습니다.
Graph간 argument 전달이 어려운 것도 아쉬웠습니다. 하나의 Graph에 다른 Graph를 include 하였을 때 argument 전달이 불가능하였습니다. Nested 형태로 사용하게 되도 동일하지만 실제 XML 코드에 argument를 추가하면 전달이 되는 것은 확인 하였습니다.

Fragment 간 Data를 전달받는 방법은 아래와 같이 LiveData를 이용하는 방법으로 가이드가 되어 있었는데 boilerplate 코드도 많고 좀 더 효율적인 방법이 없을까 하는 아쉬움이 있었습니다. (혹시 다른 방법이 있다면 알려주세요!)
findNavController().previousBackStackEntry?.savedStateHandle?.set("confirm", false)>>>>override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val savedStateHandle = findNavController().currentBackStackEntry?.savedStateHandle ?: return savedStateHandle.getLiveData<Boolean>("confirm").observe(viewLifecycleOwner) { binding.message.text = "confirm: $it" } }
Safe Args는 명확하게 Argument 정보를 알 수 있어 좋지만 반대로 Deeplink 등 을 통해 argument 없이 Fragment에 접근하게 되면 Crash가 발생하게 됩니다.

샘플 코드를 작성하면서 느꼈던 것은 내비게이션이나 지도, 카메라 등의 Single Activity를 사용하는 앱에서는 굉장히 유용하겠다는 느낌을 많이 받았습니다. args 전달, deeplink, animation 설정 등의 다양한 기능도 좋지만 무엇보다 stack 관리를 해준다는 것이 굉장히 매력적인 점이라고 생각이 되었습니다.
꼭 Single Activity가 아니더라도 지금 서비스하고 있는 앱의 시나리오를 다시 한번 정리해 보면서 기능에 맞게 Activity/Fragment로 리팩토링하고 Navigation Component를 적용해 보는 것도 좋을 것이라 생각됩니다.