[Android] Jetpack Navigation — Interaction

Kenneths
Kenneth Android
Published in
13 min readJan 7, 2023
Photo by Tianyi Ma on Unsplash

안녕하세요 이번글에서는 코드를 사용한 내비게이션 요소들을 활용한 방법과 내비게이션 컴포넌트에서 다루는 상호작용 관련된 내용에 대해서도 다루려고 합니다.

Interact programmatically

이 내용은 Interact programmatically를 번역해서 작성하였습니다.

내비게이션 컴포넌트는 내비게이션의 요소에 대해서 프로그래매틱하게 생성하고 상호작용하는 방법을 제공합니다.

NavBackStackEntry를 사용하여 Destination 참조

내비게이션 2.2.0 버전 이상을 사용한다면 NavController.getBackStackEntry()를 Destination ID과 함께 호출하면 내비게이션 스택에 있는 어떠한 Destination을 참조할 수 있는 NavBackStackEntry를 가져올 수 있습니다. 만약 특정 Destination이 백스택에 1개 이상을 포함하고 있다면 getBackStackEntry()는 스택에서 가장 상위에 있는 인스턴스를 리턴합니다.

전달받은 NavBackStackEntry객체는 해당 Destination 레벨의 Lifecycle, ViewModelStore, SavedStateRegistry 를 제공합니다. 이 객체들은 백스택에 있는 Destination의 라이프타임동안만 유효합니니다. Destination이 백스택에서 꺼내질 때 Lifecycle은 파괴되는걸 생각해볼 수 있습니다. 상태는 더 이상 저장할 것이 없기 때문에 ViewModel객체도 클리어가 됩니다.

이 프로퍼티들은 Lifecyle과 저장을 위한 ViewModel객체들을 줍니다.

Lifecycle, ViewModel객체나 클래스의 저장소와 같이 이 프로퍼티들을 사용하면 Destination의 타입과는 관계없이 상태를 저장할 수 있습니다. 이 기능은 커스텀 Destination처럼 자동으로 라이프사이클과 연결되지않은 Destination 타입들끼리 작업할 때 유용합니다.

예를들어 NavBackStackEntryLifecycle을 관찰한다고 가정했을 때 간단하게 Fragment 혹은 Activity의 Lifecycle을 관찰하게 됩니다. 추가적으로 NavBackStackEntryLifecycleOwner 입니다. 즉, 다음 예시와 같이 LiveData 를 관찰하거나 다른 lifecycle-aware 컴포넌트들과 함께 사용할 수 있다는 것을 의미합니다.

라이프사이클 상태는 navigate()가 호출될 때 자동으로 업데이트됩니다. 백스택의 맨 위에 있지 않은 Destination에 대한 라이프사이클 상태는 RESUMED에서 STARTED로 이동하고 만약 Dialog Destination처럼 FloatingWindow Destination아래에 보이는 Destination들은 STOPPED 로 이동합니다.

이전 Destination으로 결과를 리턴받기

내비게이션 2.3 이상의 버전을 사용한다면 NavBackStackEntrySavedStateHandle 에 접근할 수 있습니다. SavedStateHandle은 데이터를 저장하고 되찾을 수 있는 Key-Value로 이루어진 Map 형태입니다. 이 값은 프로세스가 죽거나 구성 변경(Configuration Changes)이 발생해도 사용가능한 동일한 객체로 유지합니다. SavedStateHandle을 사용한다면 Destination들 간에 데이터를 주고 받을 수 있습니다. 이런 특별한 매커니즘은 Destination이 스택에서 팝되었을 때 데이터를 가져오기 편리합니다.

Destination B에서 Destination A로 백했을 때 데이터를 전달하기 위해서 먼저 DestinationA의 SavedStateHandle 의 결과를 수신해야합니다. getCurrentBackStackEntry()API를 사용해서 NavBackStackEntry를 가져오고 SavedStateHandle이 제공하는 LiveDataobserve합니다.

Destination B는 getPreviousBackStackEntry()를 사용해서 이전 Destination A의 NavBackStackEntry에 접근할 수 있습니다.

Destination B에서는 getPreviousBackStackEntry() API를 사용해서 Destination A의 SavedStateHandle에 결과를 꼭 set 해주어야 합니다.

NavBackStackSavedStateHandle을 사용하므로 모든 결과는 Bundle에 사용할 수 있는 형태여야합니다. Parcelable이나 Serializable이 아닌 커스텀 타입들의 결과를 저장하려면 이전 NavBackStackEntry에서 ViewModelStore를 사용한 ViewModel을 만듭니다. ViewModel에 자세한 사용법은 ViewModel 가이드를 참조해주세요

만약에 결과값을 한번만 핸들링하길 바란다면 SavedStateHandleremove()를 호출하여 결과를 삭제해야합니다. 결과값을 지우기를 바라지 않는다면 LiveData에 접근한 새로운 Observer인스턴스에 지속적으로 마지막 결과값을 반환해줍니다.

ViewModel을 사용한 Destination간에 UI 관련 데이터 공유

내비게이션 백스택은 각 Destination뿐만 아니라 각 Destination의 부모 내비게이션 그래프에 대해서도 NavBackStackEntry를 저장합니다. 이렇게하면 범위가 지정된 내비게이션 그래프에서 NavBackStackEntry를 검색할 수 있습니다. 범위가 지정된 내비게이션 그래프의 NavBackStackEntry는 범위가 지정된 내비게이션 그래프에서 ViewModel을 생성하고 그래프의 각 Destination간에 UI관련된 데이터들을 공유할 수 있게 해줍니다. 이렇게 생성된 ViewModel 객체는 연결된 NavHostViewModelStore가 지워지거나 백스택에서 탐색그래프가 팝업이 되기전까지 유지됩니다

아래 예제는 내비게이션 그래프에서 범위가 지정된 ViewModel를 검색하는 방법을 나타냅니다

Navigation 2.2.0 이전 버전을 사용한다면 다음 예제처럼 Saved State with ViewModels를 사용해서 고유의 팩토리를 제공해야합니다.

popUpTo 와 popUpToInclusive

Action을 사용해서 이동할 때 백스택에서 추가 Destination을 Pop 하도록 선택할 수 있습니다. 예를 들어 앱에서 초기 로그인으로 진입하는 그래프나 Destination이 있다면 사용자가 로그인 한 후 뒤로버튼을 누르거나 기존에 로그인 흐름을 지우고 다음 화면으로 넘어가야할 상황에서는 로그인 관련된 Destination들을 Pop해야 하는 상황이 발생합니다.

만약 Destination에서 다른 Destination으로 이동할 때 Destination을 Pop하려면 <action>엘리먼트에 app:popUpTo 속성을 추가합니다. app:popUpTonavigate() 호출의 일부로 백스택에서 몇 개의 Destination을 팝하도록 내비게이션 라이브러리에게 알려줍니다. 해당 프로퍼티 값(route or id)은 스택애 잔존해야하는 가장 상위의 Destination ID입니다.

또한 app:popUpToInclusive="true"로 설정하면 app:popUpTo에 지정된 Destination 까지 백스택에서 삭제됩니다.

popUpTo 예: 순환 로직

앱에서 A,B C 세 개의 Destination과 A->B->C->A 로 연결되는 작업이 있을 때 계속해서 A->B->C->A를 반복한다면 여러 Destination이 중첩되어 사용자 경험이 나빠질 수 있습니다. 이런 반복의 작업을 피하려면 C에서 A로 이동할 때 app:popUpToapp:popUpToInclusive를 지정해서 사용하면 해결됩니다.

위와 같이 설정하면 Destination C에서 A로 navigate() 호출한다고 가정했을 때, popUpTo만 설정했을 때 C->A로 내비게이트가 발생하면 A가 나오기 이전의 백스택들은 팝 대상이됩니다. (예를들어 A->B->C->A는 C와 B가 백스택에서 제외되어 A->A가 백스택에 남게됩니다) 만약 popUpToInclusive까지 true로 설정한다면 백스택에서 제거하는 대상이 A의 이전 스택까지가 아니라 A도 포함하여 팝의 대상이 되기 때문에 위와 같은 시나리오에서는 A만 스택에 남게됩니다 .

Multiple Backstack

내비게이션 컴포넌트는 사용자가 앱에서 이동할 때 안드로이드 OS와 연동하여 백스택을 유지합니다. 사용자가 앞뒤로 이동하는 상황에서는 여러 백스택을 유지하는게 도움이 될 수 있습니다. 예를들어 Bottom Navigation이나 Navigation Drawer를 포함한 경우 백스택 지원을 사용하면 사용자가 앱의 이동하는 흐름에서 자신의 위치를 잃어버리지 않고 자유롭게 전환이 가능합니다.

내비게이션 컴포넌트는 내비게이션 그래프에서 Destination의 상태는 저장하고 복원할 수 있게 멀티플 백스택을 지원하는 API를 제공합니다. NavigationUI 클래스는 이를 자동으로 처리하는 메서드가 포함되지만 기본 API를 이용해서 조금 더 커스터마이징하게 사용할 수 있습니다.

NavigationUI를 사용하여 자동으로 지원

NavigationUI 클래스에는 유저의 메뉴 아이템간의 이동할 때 메뉴의 상태를 자동으로 저장하고 복원하는 API를 포함하고 있습니다. 이런 API는 다음과 같은 사례처럼 자동적으로 멀티플 백스택을 제공합니다.

기본 API를 사용하여 직접 구현

Navgation XML
내비게이션 그래프의 <action> 엘리먼트에 app:popUpToSaveState 속성을 사용하여 app:popUpTo로 백스택에서 제거한 Destination의 상태를 저장할 수 있습니다. 또한 app:restoreState속성을 사용하여 app:destination 속성에 정의된 Destination의 이전 저장 상태를 복원할 수 있습니다.

이러한 속성들을 이용해서 멀티플 백스택을 지원하면 됩니다. <action>엘리먼트에 app:popUpToSaveStateapp:restoreState를 true로 설정하면 현재 백스택의 상태를 저장함과 동시에 이전에 저장된 Destination의 백스택 상태가 있는 경우 상태를 복원합니다.

NavOptions
NavOptions 클래스를 사용하면 NavController를 사용하여 이동할 때 백스택을 저장하고 복원할 수 있습니다. NavOptions 인스턴스를 생성하는 방법은 Kotlin DSL 또는 NavOptions.Builder를 사용할 수 있습니다.

NavOptions.Builder

NavOptions의 새로운 인스턴스를 생성하기 위한 빌더

  • launchSingleTop : 내비게이션 액션이 SingleTop으로 실행되어야 하는지에 대한 여부입니다. (백스택 상단에 최대 1개의 주어진 Destination이 있습니다.) 이 함수는 액티비티에서 android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP 이 동작하는 방식과 유사합니다.
  • restoreState : PopUpToBuilder.saveState 또는 popUpToSaveState 어트리뷰트를 사용해서 내비게이션 액션이 이전에 저장된 어떠한 상태를 복원할 수 있는지에 대한 여부입니다. 만약 탐색중인 Destination ID에서 이전에 저장된 상태가 없다면 아무런 효과를 발생하지 않습니다.

PopUpToBuilder

NavOptionsBuilder.popUpTo 작업을 커스터마이징 하기 위한 DSL

  • inclusive: popUpTo의 Destination이 백스택에서 팝되어야 하는지에 대한 여부입니다.
  • saveState : 현재 Destination과 NavOptionsBuilder.popupTo ID 사이의 모든 Destination의 상태와 백스택여부입니다. 나중에 NavOptionsBuilder.restoreState 혹은 restoreState attribute 를 사용하여 동일한 NavOptionsBuilder.popUpTo 아이디를 복구합니다. (여기에 매칭된 ID는 inclusive가 true거나 false과 상관없이 항상 true 입니다.)

마무리

지금까지 내비게이션 컴포넌트들에 여러 요소들에 대해서 알아봤습니다. 안드로이드도 과거의 Activity보다 Fragment를 활용한 SingleActivity, Jetpack Compose로만 화면을 구현했을 때 Navigation 컴포넌트를 활용하면 이점이 많기 때문에 정확한 사용방법을 익히고 여러 요소들을 학습하여 프로덕트에 활용하면 좋겠습니다. 긴 글 읽어주셔서 감사합니다 🙇

목차

  1. [Android] Jetpack Navigation — Migrate to Compose — Overview
  2. [Android] Jetpack Navigation — Migrate to Compose — Graph & Destination
  3. [Android] Jetpack Navigation — Migrate to Compose — DeepLink
  4. [Android] Jetpack Navigation — Migrate to Compose — Animation
  5. [Android] Jetpack Navigation — Interaction

참고

--

--

Kenneths
Kenneth Android

사용자들에게 편리하고 AweSome UI, UX를 경험해주고 싶은 상위 티어 개발자가 되고싶어 달려가고있는 개발자입니다. 다양한 내용들을 공유하려고 합니다.