본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 10. 03:46

화면 간 데이터 전달이 쉬울 줄 알았는데, 그렇지 않았다.

요약

앱 네비게이션 과정에서 데이터를 처리하는 근본적인 원리를 설명합니다. 전체 객체를 직접 전달할 수 없고, 최소한의 ID와 같은 기본 타입만 전달해야 하는 이유를 명확히 합니다. 또한, 이전 화면으로 데이터를 되돌려 보내는 방법까지 다룹니다.

핵심 포인트

  • 네비게이션은 문자열 경로(URL) 기반이므로 전체 객체 전달 불가
  • 데이터 이동 시에는 최소한의 ID와 같은 기본 타입만 사용해야 함
  • 백 스택 재구성을 위해 경로는 반드시 문자열로 유지되어야 함

제가 이번 주가 어떻게 흘러갔는지 정확히 말씀드리겠습니다.

첫째 날: 단순히 ID만 전달하는 대신 전체 Product 객체를 네비게이션을 통해 전달하려고 시도했습니다. 앱은 정상적으로 컴파일되었습니다. 그러다가 제품을 탭하는 순간 크래시가 발생했습니다. 도움이 되는 메시지는 전혀 없었습니다. 그냥 사라졌습니다.

둘째 날: 이번에는 ID를 올바르게 전달했습니다. 지난 글에서 배운 내용을 적용했죠. 하지만 상세 화면으로 이동한 후, 이상한 느낌이 들었습니다. 데이터가 실제로 어디에 존재하고 있는 걸까요? 설명할 수 없었습니다. 그저 backStackEntry.arguments에 있을 거라고 믿었을 뿐입니다. 때로는 그랬고, 때로는 null 값을 받았으며, 왜 그런지 전혀 알 수 없었습니다.

셋째 날: 데이터를 앞으로 보내는 방법은 잘 파악했습니다. 하지만 사용자가 선택한 후 이전 화면으로 데이터를 되돌려 보내야 할 필요가 생겼습니다. 그리고 저는 그 화면을 아주 오랫동안 쳐다봤는데, 제가 배운 어떤 것도 이 방향에 대비시켜 주지 못했기 때문입니다.

이 세 가지 일은 모두 같은 프로젝트의 같은 주 안에 저에게 일어났습니다. 그리고 이 모든 것은 하나의 근본적인 문제에서 비롯되었습니다. 바로 네비게이션이 데이터를 어떻게 처리하는지에 대해 실제로 이해하기보다 추측하고 있었던 것입니다.

이 글은 저와 우리 모두를 위해 그 '추측'을 멈추는 내용입니다.

저희는 지난 글의 이커머스 앱(e-commerce app)을 이어갑니다: 제품 목록 → 제품 상세 → 장바구니. 만약 그 글을 읽지 않으셨다면, 간단히 말해 다음과 같습니다. 저희는 경로를 위한 sealed class, NavHost, 그리고 NavController를 가지고 있습니다. 이 설정은 정확하게 동일합니다. 오늘 우리가 할 일은 데이터가 이 화면들 사이에서 이동할 때 발생하는 모든 것을 이해하는 것입니다.

전체 객체를 그냥 전달할 수 없는 이유
이것은 대부분의 사람들이 시작하는 지점이며, 동시에 가장 먼저 벽에 부딪히는 곳입니다.
제품(Product)이 있습니다. 상세 화면은 제품을 필요로 합니다. 그냥... 전달하면 안 될까요?
kotlin// 이것은 논리적으로 느껴집니다. 하지만 그렇게 하지 마세요.
navController.navigate(Screen.ProductDetail.createRoute(product))
작동하지 않는 이유가 여기에 있습니다.
네비게이션 경로는 문자열입니다. 그것이 시스템의 핵심입니다. navController.navigate(...)를 호출할 때, 당신은 URL과 같은 문자열 주소로 이동하는 것입니다. "product_detail/1"은 유효한 주소입니다. "product_detail/Product(id=1, name=Air Max 90, price=120.0)"는 그렇지 않습니다.
시스템은 객체를 URL을 통해 직렬화(serialize)하고 역직렬화(deserialize)하도록 구축되지 않았습니다. 그리고 설령 JSON 인코딩 같은 우회 방법을 찾는다 하더라도, 그것은 시스템을 사용하는 대신 아키텍처와 싸우는 것이 됩니다.
또한 더 깊은 이유도 있습니다. 네비게이션 스택의 화면들은 경로(route)만으로 재구성될 수 있기 때문입니다. 만약 Android가 프로세스를 종료시키고 사용자가 돌아온다면, 백 스택(back stack)을 다시 구축할 수 있어야 합니다. 문자열 ID는 이것을 할 수 있습니다. 메모리에 떠다니는 전체 객체는 할 수 없습니다.
따라서 규칙은 간단합니다: 최소한의 것만 전달하세요. ID를 전달하세요.
kotlin// 이것이 올바른 사고방식입니다.
// ID는 단지 주소일 뿐입니다. 화면이 가서 전체 데이터를 가져옵니다.
navController.navigate(Screen.ProductDetail.createRoute(product.id))

기본 타입(Primitives): 실제로 무슨 일이 일어나고 있는가
이전 글에서는 productId: Int를 전달하고 넘어갔습니다. 하지만 이 부분이 바로 null의 미스터리가 발생하는 곳이니, 속도를 늦추고 실제로 무슨 일이 일어나는지 읽어보겠습니다.
다음은 라우트 정의입니다:

object ProductDetail : Screen("product_detail/{productId}") {
    fun createRoute(productId: Int) = "product_detail/$productId"
}

라우트 템플릿의 {productId}는 플레이스홀더(placeholder)입니다. 이는

선택적 인자 및 기본값
문서에서 명확하게 알려주지 않는 부분이 있습니다: 인자는 선택적일 수 있다는 것입니다.
기본적으로 라우트의 {placeholder}는 필수입니다. 만약 존재하지 않으면 탐색(navigation)이 실패합니다. 하지만 때로는 특정 데이터 없이도 잘 작동하는 화면이 있습니다. 예를 들어, 초기 쿼리(query)가 있든 없든 열릴 수 있는 검색 화면 같은 경우입니다.
저희 이커머스 앱에서 장바구니(Cart) 화면은 할인 필드를 미리 채우는 couponCode를 선택적으로 받을 수 있다고 가정해 봅시다:
kotlinobject Cart : Screen("cart?couponCode={couponCode}") {
fun createRoute(couponCode: String? = null): String {
return if (couponCode != null) "cart?couponCode=$couponCode"
else "cart"
}
}
여기서 두 가지가 변경되었습니다. 첫째, 구문입니다. 선택적 인자는 경로 형식(/{value}) 대신 쿼리 매개변수 형식(?key={value})을 사용합니다. 시스템이 이 인자가 선택적이라는 것을 아는 방법입니다.
둘째, createRoute()가 두 가지 형태를 갖게 되었습니다. 쿠폰 코드가 있는 경우와 없는 경우입니다. 둘 다 "장바구니 화면(the cart screen)"으로 탐색하지만, 단지 컨텍스트만 다릅니다.
NavHost 등록:
kotlincomposable(
route = Screen.Cart.route,
arguments = listOf(
navArgument("couponCode") {
type = NavType.StringType
nullable = true
defaultValue = null
})
) { backStackEntry ->
val couponCode = backStackEntry.arguments?.getString("couponCode")
CartScreen(couponCode = couponCode, navController = navController)
}
nullable = truedefaultValue = null이 이 인자를 선택적으로 만드는 요소입니다. 이것들이 없으면 시스템은 해당 인자가 존재할 것을 요구합니다.
이제 장바구니로 두 가지 방식으로 탐색할 수 있습니다:
kotlin// ProductDetail에서 — 쿠폰 없음
avController.navigate(Screen.Cart.createRoute())

// 프로모션 배너에서 — 미리 채워진 쿠폰 포함
avController.navigate(Screen.Cart.createRoute(couponCode = "SAVE20"))
두 방식 모두 작동합니다. 장바구니 화면은 couponCode가 null인지 아닌지에 따라 두 경우를 처리합니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0