[Android] @Serializable이 있는데 왜 Intent로 객체를 넘길 수 없을까? (feat. Parcelable)

gay0ung·2025년 7월 23일
0

Android

목록 보기
5/14

안드로이드 개발 중 Intent로 커스텀 객체를 전달하려 할 때, 많은 개발자들이 런타임 에러를 경험하곤 한다. 나 역시 UserDataDto라는 데이터 클래스를 다른 Activity로 전달하는 과정에서 아래와 같은 에러를 마주쳤다.

java.lang.RuntimeException: Parcel: unable to marshal value com.example.myapp.UserDataDto

분명 내 UserDataDto 코드에는 @Serializable 어노테이션이 붙어있었는데, 왜 이런 문제가 발생한 걸까? 이 글은 그 혼란의 원인과 명쾌한 해결책을 다룬다.

원인: 두 종류의 'Serializable'

문제의 핵심은, 안드로이드 개발에서 마주치는 'Serializable'이 사실상 두 가지 종류라는 점에 있었다.

1. kotlinx.serialization.Serializable

  • 정체: 코틀린 공식 직렬화 라이브러리 (org.jetbrains.kotlinx:kotlinx-serialization-json)
  • 용도: 객체를 JSON, Protobuf 등 특정 데이터 포맷으로 변환하거나 그 반대의 작업을 할 때 사용. 주로 Retrofit, Ktor 같은 네트워킹 라이브러리나 Room DB와 함께 쓰인다.
  • 특징: 컴파일 타임에 코드를 생성하여 리플렉션 없이 매우 빠르게 동작한다.
  • 결론: 네트워크 통신이나 파일 저장을 위한 것, 안드로이드 Intent를 위한 것이 아니다.

2. java.io.Serializable

  • 정체: 자바 표준 라이브러리(java.io)
  • 용도: 객체를 범용적인 바이트 스트림으로 변환. 이 덕분에 Intent에 담아 전달하는 것이 가능해진다.
  • 특징: 구현은 간단하지만, 런타임에 리플렉션을 사용해 성능이 매우 느리다.

내 코드에 있던 @Serializable은 바로 1번, kotlinx.serialization.Serializable이었다. 따라서 JSON 변환은 가능했지만, 안드로이드 시스템이 Activity 간 데이터 전달을 위해 필요로 하는 바이트 스트림으로의 변환은 불가능했던 것이다. 이것이 에러의 진짜 원인이었다.

해결책: 안드로이드의 공식 추천, Parcelable

안드로이드에서는 컴포넌트 간 데이터 전달(IPC)을 위해 Serializable보다 훨씬 빠르고 효율적인 Parcelable 인터페이스를 사용하도록 강력히 권장한다.

과거에는 Parcelable을 구현하기 위해 writeToParcel, createFromParcel 등 복잡한 코드를 직접 작성해야 했지만, 코틀린에서는 @Parcelize 어노테이션 하나로 모든 것이 해결된다.

적용 방법

kotlinx.serialization의 장점과 Parcelable의 장점을 모두 취하기 위해, 두 가지를 함께 사용하면 된다.

1. build.gradle.kts (app 모듈)에 플러그인 추가

plugins 블록 안에 kotlin-parcelize가 있는지 확인하고, 없다면 추가한다.

// app/build.gradle.kts

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("kotlin-parcelize") // 이 라인을 추가 (또는 확인)
}

2. 데이터 클래스 수정

기존 @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로 빠르게 전달 가능

결론

  • @Serializable 어노테이션이 있다고 해서 Intent로 객체를 바로 전달할 수 있는 것은 아니라는 걸 인지해야 한다. (kotlinx.serialization vs java.io)
  • 네트워크/DB 통신을 위해서는 kotlinx.serialization.Serializable을 사용한다.
  • 안드로이드 컴포넌트 간 데이터 전달을 위해서는 Parcelable을 사용한다.
  • 코틀린의 @Parcelize 어노테이션을 사용하면 Parcelable 구현이 매우 쉽다.
  • 두 가지 목적이 모두 필요하다면, 함께 사용하는 것이 최선의 방법이다.

이제부터 Intent로 객체를 넘길 때 에러가 발생하면, Parcelable 구현이 제대로 되어 있는지 가장 먼저 확인해보는 습관을 들이는 것이 좋겠다.

0개의 댓글