안드로이드 개발 중 Intent로 커스텀 객체를 전달하려 할 때, 많은 개발자들이 런타임 에러를 경험하곤 한다. 나 역시 UserDataDto라는 데이터 클래스를 다른 Activity로 전달하는 과정에서 아래와 같은 에러를 마주쳤다.
java.lang.RuntimeException: Parcel: unable to marshal value com.example.myapp.UserDataDto
분명 내 UserDataDto 코드에는 @Serializable 어노테이션이 붙어있었는데, 왜 이런 문제가 발생한 걸까? 이 글은 그 혼란의 원인과 명쾌한 해결책을 다룬다.
문제의 핵심은, 안드로이드 개발에서 마주치는 'Serializable'이 사실상 두 가지 종류라는 점에 있었다.
kotlinx.serialization.Serializable
java.io.Serializable
내 코드에 있던 @Serializable은 바로 1번, kotlinx.serialization.Serializable이었다. 따라서 JSON 변환은 가능했지만, 안드로이드 시스템이 Activity 간 데이터 전달을 위해 필요로 하는 바이트 스트림으로의 변환은 불가능했던 것이다. 이것이 에러의 진짜 원인이었다.
안드로이드에서는 컴포넌트 간 데이터 전달(IPC)을 위해 Serializable보다 훨씬 빠르고 효율적인 Parcelable 인터페이스를 사용하도록 강력히 권장한다.
과거에는 Parcelable을 구현하기 위해 writeToParcel, createFromParcel 등 복잡한 코드를 직접 작성해야 했지만, 코틀린에서는 @Parcelize 어노테이션 하나로 모든 것이 해결된다.
kotlinx.serialization의 장점과 Parcelable의 장점을 모두 취하기 위해, 두 가지를 함께 사용하면 된다.
build.gradle.kts
(app 모듈)에 플러그인 추가plugins 블록 안에 kotlin-parcelize가 있는지 확인하고, 없다면 추가한다.
// app/build.gradle.kts
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("kotlin-parcelize") // 이 라인을 추가 (또는 확인)
}
기존 @Serializable은 그대로 두고, @Parcelize 어노테이션과 Parcelable 인터페이스를 추가한다.
수정 전 코드
// kotlinx.serialization.Serializable만 존재
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class UserDataDto(
@SerialName("profile")
val profile: UserProfile? = null,
)
@Serializable
data class UserProfile(
@SerialName("name")
val name: String? = null,
// ...
)
수정 후 코드
import android.os.Parcelable // Parcelable 임포트
import kotlinx.parcelize.Parcelize // Parcelize 임포트
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable // JSON 변환을 위해 유지
@Parcelize // Intent 전달을 위해 추가
data class UserDataDto(
@SerialName("profile")
val profile: UserProfile? = null,
) : Parcelable // Parcelable 인터페이스 구현
@Serializable // JSON 변환을 위해 유지
@Parcelize // Intent 전달을 위해 추가
data class UserProfile(
@SerialName("name")
val name: String? = null,
@SerialName("email")
val email: String? = null,
@SerialName("phone")
val phone: String? = null,
@SerialName("address")
val address: String? = null,
) : Parcelable // Parcelable 인터페이스 구현
이렇게 수정하면 UserDataDto 객체는 두 가지 능력을 모두 갖추게 된다.
@Serializable
: 네트워크 통신 시 JSON으로 빠르게 변환 가능@Parcelize
+ Parcelable
: Intent에 담아 다른 Activity로 빠르게 전달 가능Parcelable
을 사용한다.@Parcelize
어노테이션을 사용하면 Parcelable 구현이 매우 쉽다.이제부터 Intent로 객체를 넘길 때 에러가 발생하면, Parcelable 구현이 제대로 되어 있는지 가장 먼저 확인해보는 습관을 들이는 것이 좋겠다.