[Android] Jetpack Compose에서 Activity를 안 쓰는데, Parcelable이 왜 필요할까?

gay0ung·2025년 7월 23일
0

Android

목록 보기
9/14

Jetpack Compose로 앱을 개발하다 보면 이런 생각이 들 수 있다. "이제 화면을 모두 Composable 함수로 만드니까, Activity나 Fragment를 쓸 때처럼 Intent에 데이터를 담아 보낼 일도 없겠지? 그럼 Parcelable도 더는 필요 없는 거 아닐까?"

결론부터 말하면, 그렇지 않다.

Jetpack Compose의 Navigation 라이브러리를 사용해 Composable 화면(Screen) 간에 커스텀 객체를 전달해야 한다면, Parcelable은 여전히 가장 중요하고 효율적인 방법이다. 이 글에서는 그 이유를 Compose Navigation의 내부 동작과 함께 파헤쳐 보고자 한다.

Compose Navigation의 데이터 전달 방식

Compose Navigation에서는 NavController를 사용해 경로(route)를 기반으로 화면을 이동한다.

navController.navigate("detail/12345")

String, Int 같은 간단한 원시 타입 데이터는 위 예시처럼 경로 문자열에 직접 포함시켜 간단하게 전달할 수 있다. 하지만 User나 Product 같은 복잡한 커스텀 객체를 전달해야 할 때는 어떻게 해야 할까? 객체를 통째로 JSON 문자열로 변환해서 경로에 꾸역꾸역 집어넣는 것은 보기에도 좋지 않고, 데이터가 크면 URL 길이 제한으로 앱이 비정상 종료될 수도 있다.

숨겨진 조력자: Bundle의 등장

바로 이 지점에서 안드로이드의 전통적인 데이터 꾸러미, Bundle이 등장한다.

Jetpack Compose Navigation 라이브러리는 화면의 상태를 저장하고 복원하기 위해, 내부적으로 안드로이드 프레임워크의 Bundle을 사용한다.

Bundle은 원래 Activity나 Fragment의 상태를 저장하기 위해 설계된 핵심 컴포넌트다. 사용자가 화면을 회전시키거나, 잠시 앱을 나갔다 돌아오거나, OS가 메모리 확보를 위해 앱 프로세스를 종료했다가 다시 시작하는 등 UI가 예기치 않게 파괴되었다가 재생성될 때, Bundle에 저장된 데이터를 사용해 이전 상태를 그대로 복원한다.

Compose Navigation 역시 이 강력한 메커니즘을 그대로 활용한다. 우리가 navController를 통해 화면을 이동할 때 전달하는 인자(arguments)들은 NavBackStackEntry 내의 Bundle에 안전하게 보관된다.

Bundle과 Parcelable의 필연적인 관계

그렇다면 Bundle에 우리가 만든 커스텀 객체를 어떻게 넣을 수 있을까? Bundle은 아무 객체나 받아주지 않는다. Bundle에 담기려면 객체를 바이트 스트림으로 변환(직렬화)할 수 있어야 한다.

그리고 안드로이드에서 객체를 직렬화하는 가장 효율적이고 빠른 방법이 바로 Parcelable이다.

  • Serializable: 자바 표준이지만, 리플렉션을 사용해 안드로이드에서 매우 느리다.
  • Parcelable: 안드로이드 SDK에 포함되어 있으며, IPC(프로세스 간 통신)와 상태 저장을 위해 특별히 설계되어 성능이 월등히 빠르다.

결국, 이런 논리적 흐름이 완성된다.

  1. Composable 화면 간에 커스텀 객체를 안전하게 전달하고 싶다.
  2. Compose Navigation은 전달된 데이터를 Bundle에 저장하여 상태를 관리한다.
  3. Bundle에 커스텀 객체를 효율적으로 저장하려면 Parcelable 구현이 필수적이다.

실전 코드: Compose에서 Parcelable 객체 전달하기

1. 데이터 클래스에 @Parcelize 적용

가장 먼저, 전달할 데이터 클래스가 Parcelable을 구현하도록 수정한다.

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize // @Parcelize 어노테이션 추가
data class User(val id: String, val name: String) : Parcelable

2. Navigation-Compose 라이브러리 활용

Compose Navigation은 Parcelable 타입을 직접 지원하므로, 네비게이션 그래프를 만들 때 인자 타입을 지정해주기만 하면 된다. (이를 위해선 navigation-compose 라이브러리 의존성이 추가되어 있어야 한다)

// NavHost를 설정하는 부분
NavHost(navController = navController, startDestination = "profile") {
    composable("profile") {
        // 프로필 화면 UI
    }

    // User 객체를 인자로 받는 detail 화면 정의
    composable(
        route = "detail",
        // arguments 리스트에 User 타입을 추가
        arguments = listOf(navArgument("user") { type = NavType.ParcelableType(User::class.java) })
    ) { backStackEntry ->
        // Bundle에서 Parcelable 객체 꺼내기
        val user = backStackEntry.arguments?.getParcelable<User>("user")
        if (user != null) {
            DetailScreen(user = user)
        }
    }
}

3. 객체와 함께 화면 이동 호출

객체를 전달할 때는 currentBackStackEntry의 arguments에 Bundle처럼 값을 넣고, 경로로 이동하면 된다.

// 버튼 클릭 등 이벤트 핸들러 내부
val user = User(id = "user-123", name = "홍길동")

// 현재 백스택의 arguments에 user 객체를 넣음
navController.currentBackStackEntry?.arguments?.putParcelable("user", user)
navController.navigate("detail")

결론

"Activity를 안 쓴다"는 생각에 Parcelable을 외면해서는 안 된다. 우리가 작성하는 Composable 코드는 결국 안드로이드라는 큰 운영체제 위에서 실행되며, Compose 라이브러리 역시 안정성과 성능을 위해 안드로이드의 검증된 시스템(Bundle)을 적극적으로 활용하고 있다.

따라서 Jetpack Compose로 앱을 만들 때도, 화면 간에 커스텀 객체를 전달해야 한다면 Parcelable을 사용하는 것이 성능, 안정성, 그리고 안드로이드 생태계의 표준을 모두 지키는 최선의 방법이라고 할 수 있다.

0개의 댓글