깨알 개념/Android

[Android] Jetpack Compose - Navigation의 Destination 간 데이터 전달 (NavBackStackEntry.arguments)

interfacer_han 2024. 9. 13. 13:54

#1 이전 글

 

[Android] Jetpack Compose - Navigation 기초

#1 개요#1-1 전통적인 안드로이드 프로젝트에서의 Navigation [Android] Navigation - 기초#1 이전 글 [Android] Navigation - 환경 설정 #1 Navigation#1-1 액티비티 및 프래그먼트 구성의 트렌드요즘 안드로이드 개발

kenel.tistory.com

본 게시글에선 위 게시글의 완성된 앱을 일부 수정하여, Destination끼리 서로 데이터를 전달하게 만들어본다.
 
이를 위해서 먼저 NavBackStackEntry에 대해 알아야 한다. 이름에서 보듯, NavBackStackEntry은 백 스택을 관리하는 객체다. 따라서, Back Stack(백 스택)이 뭔지 알아야 NavBackStackEntry를 이해할 수 있다.
 

#2 Back Stack

#2-1 일반적인 개념

백 스택은 마치 웹 브라우저에서의 방문 기록과 같다고 할 수 있다. 웹 서핑을 하다가 뒤로 가기 버튼을 누르면 가장 최근에 방문한 페이지부터 가장 예전에 방문했던 페이지 순으로 이동하는 것과 같이, 안드로이드 앱 구조에서도 뒤로 가기 버튼을 누르면 가장 최근에 '방문'한 화면에서 가장 예전에 방문했던 페이지로 이동한다 (계속 뒤로 가기 버튼을 누르다보면 더 이상 방문했던 화면이 남아있지 않게될텐데, 여기서 한번 더 뒤로 가기 버튼을 누르면 앱이 종료된다).
 

#2-2 안드로이드 프로젝트에서의 개념

이전 게시글(#1)의 완성된 앱에서 "Go to Second Screen"과 "Go to First Screen" 버튼을 보이는 대로 계속 누르면 위와 같은 백 스택이 형성된다. 맨 ↘쪽에 있는 화면에서 뒤로 가기 버튼을 누르면 오래된 쪽(↖)으로 거슬러 올라간다. 여기서 짚어야할 점이 있다. 바로 "백 스택에는 Back이라는 단어가 포함되어 있으니, '현재 보이는 화면'은 Back Stack에 포함되지 않는건가?"라는 의문이 그것이다. 아니다, 적어도 안드로이드 프로젝트에서의 Back Stack 개념에선 '현재 보이는 화면' 또한 Back Stack에 포함된다. 즉, 위 도식도에서 맨 ↘쪽에 있는 화면 또한 백스택에 포함된 상태라는 것이다.

 

#3 NavBackStackEntry - 데이터 전달

#3-1 개념

안드로이드 Navigation 라이브러리가 자동으로 생성하여 관리하는 Destination의 Back Stack 하나하나의 구성 항목(Entry)을 말한다. 각 Destination을 수식하는 메타 데이터라고도 할 수 있다. 이 객체는 Navigation 라이브러리 단에서 알아서 만들고 관리해준다. 따라서 #2-2에 있는 그림과 같은 상황에서 NavBackStackEntry는 총 6개가 암시적으로 만들어져 해당 백스택의 요소가 제거될 때까지 같이 살아 있을 것이다.

 

#3-2 Destination의 content

// in MyNavHost.kt

composable(
    route = "secondScreen",
) { backStackEntry ->
    // backStackEntry를 가지고 무언갈 하는 코드
}

이전 게시글의 #2-4에서 보듯, Destination의 content 인수는 그 데이터형이 @Composable (NavBackStackEntry) -> Unit이다. 다른 일반적인 컴포저블 함수들의 content 인수 타입이 @Composable () -> Unit인 것과 달리 말이다. 이는 위 코드와 같이 Destination의 하위 컴포저블(content)가 NavBackStackEntry를 참조할 수 있다는 말이 된다.
 
#2-2에서 Back Stack은 현재 Destination도 포함하는 개념이라고 했다. 따라서 위 코드의 backStackEntry는 현재 Destination에게 전달된 정보라는 것을 잊지 말자. "Back이라는 단어가 있으니 이전(Back) Destination에 대한 정보인가?" 라고 무심코 생각해서는 안 된다.
 

#3-3 NavBackStackEntry.arguments 구조 정의

NavBackStackEntry의 arguments 프로퍼티는 백 스택의 각 요소마다 개별적으로 저장되는 데이터다. #3-2의 Destination 코드가 arguments를 가지게 만들어 보겠다. 예를 들어 String 타입 데이터 하나와 Int 타입 데이터 하나씩을 arguments에 정의한다면, 아래와 같은 모양이 된다.
 

// in MyNavHost.kt

composable(
    // (2) route 수정
    route = "secondScreen/{sampleStringDataName}/{sampleIntDataName}",
    // (1) arguments 추가
    arguments = listOf(
        navArgument("sampleStringDataName") {
            type = NavType.StringType
        },
        navArgument("sampleIntDataName") {
            type = NavType.IntType
        }
    )
) { backStackEntry ->
    // backStackEntry를 가지고 무언갈 하는 코드
}

(1) arguments 추가
여기서, "왜 굳이 type을 NavType이라는 별도의 인스턴스까지 이용해서 명시하는가?"라는 의문이 들 수 있는데, Destination 간 데이터는 String 타입으로만 전달되기에, 안전한 타입 캐스팅을 보장해 런타임 에러를 방지하기 위함이라고 한다. 타입을 명시함으로써, 통제가 어려운 런타임 에러를 통제 가능한 컴파일 에러로 대체할 수 있는 것이다.
 
(2) route 수정 
arguments 인수를 추가한 것 뿐만 아니라 route 인수의 문자열도 위 코드와 같은 형식으로 변경한다. 왜냐하면, 데이터를 route 문자열 속에 담아 보내기 때문이다 (#3-4 참조). route 자체에 정보를 실어 보낸다는 점이, 마치 웹 프로그래밍에서 URL에 데이터를 실어 보내는 GET 요청과 비슷하다.
 
그리고 위 코드는 arguments 프로퍼티에 실제로 데이터를 담은 것이 아니라, arguments의 받기 위한 구조를 정의한 것에 불과하다. 실제로 Destination의 arguments에 데이터를 담으려면 추가 작업이 필요하다.
 

#3-4 NavBackStackEntry에 데이터(arguments) 담아 보내기

composable(
    route = "firstScreen", arguments = listOf()
) {
    FirstScreen(navigateToSecondScreen = { navController.navigate("secondScreen/HelloWorld/123") })
}

#3-3에서 Second Screen Destination의 route 인수 문자열 형식을 새롭게 작성했었다. NavHostController.navigate()에 전달할 문자열 형식도 이에 맞춘다. 여기까지해야 비로소 arguments에 데이터가 담긴다.
 

#3-5 NavBackStackEntry에서 데이터(arguments) 가져오기

// in MyNavHost.kt

composable(
    route = "secondScreen/{sampleStringDataName}/{sampleIntDataName}",
    arguments = listOf(
        navArgument("sampleStringDataName") {
            type = NavType.StringType
        },
        navArgument("sampleIntDataName") {
            type = NavType.IntType
        }
    )
) { backStackEntry ->
    SecondScreen(navigateToFirstScreen = { navController.navigate("firstScreen") })
    Log.i("interfacer_han", "${backStackEntry.arguments?.getString("sampleStringDataName")}")
    Log.i("interfacer_han", "${backStackEntry.arguments?.getInt("sampleIntDataName")}")
}

NavBackStackEntry.arguments를 참조해 데이터를 뽑아낸다. 나는 Log 메시지만 쓰고 끝냈지만, 위 코드에 있는 SecondScreen과 같은 컴포저블 함수의 생성자에 전달하는 식으로도 응용이 가능할 것이다.
 

#3-6 작동 확인 (로그 메시지)

HelloWorld
123

#3-5까지의 코드를 실행시켜보면 나오는 로그 메시지다.
 

#4 요약

NavBackStackEntry는 백 스택의 한 요소다. NavBackStackEntry가 수행하는 데이터 전달의 형식은 웹 프로그래밍에서의 GET 요청과 비슷하다.
 

#5 완성된 앱

 

android-practice/jetpack-compose/ArgumentsOfNavBackStackEntry at master · Kanmanemone/android-practice

Contribute to Kanmanemone/android-practice development by creating an account on GitHub.

github.com