Parcelable 구현 생성기

kotlin-parcelize 플러그인Parcelable 구현 생성기를 제공합니다.

Parcelable 지원을 포함하려면 앱의 build.gradle 파일에 Gradle 플러그인을 추가합니다.

Groovy

plugins {
    id 'kotlin-parcelize'
}

Kotlin

plugins {
    id("kotlin-parcelize")
}

@Parcelize로 클래스에 주석을 달면 다음 예와 같이 Parcelable 구현이 자동으로 생성됩니다.

import kotlinx.parcelize.Parcelize

@Parcelize
class User(val firstName: String, val lastName: String, val age: Int): Parcelable

@Parcelize를 사용하려면 모든 직렬화된 속성을 기본 생성자에서 선언해야 합니다. 플러그인은 클래스 본문에서 선언된 backing 필드가 있는 각 속성에 경고를 표시합니다. 또한 기본 생성자 매개변수의 일부가 속성이 아니면 @Parcelize를 적용할 수 없습니다.

클래스에 고급 직렬화 로직이 필요한 경우 컴패니언 클래스 내에 작성합니다.

@Parcelize
data class User(val firstName: String, val lastName: String, val age: Int) : Parcelable {
    private companion object : Parceler<User> {
        override fun User.write(parcel: Parcel, flags: Int) {
            // Custom write implementation
        }

        override fun create(parcel: Parcel): User {
            // Custom read implementation
        }
    }
}

#supportedtypes

@Parcelize는 다음과 같이 다양한 유형을 지원합니다.

  • 기본 유형 및 기본 유형의 박스 버전
  • 객체 및 enum
  • CharSequence String
  • Duration
  • Exception
  • Size, SizeF, Bundle, IBinder, IInterface, FileDescriptor
  • SparseArray, SparseIntArray, SparseLongArray, SparseBooleanArray
  • 모든 Serializable(Date 포함) 및 Parcelable 구현
  • 지원되는 모든 유형의 컬렉션: List(ArrayList에 매핑됨), Set(LinkedHashSet에 매핑됨), Map(LinkedHashMap에 매핑됨)
    • 여러 구체적인 구현: ArrayList, LinkedList, SortedSet, NavigableSet, HashSet, LinkedHashSet, TreeSet, SortedMap, NavigableMap, HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap
  • 지원되는 모든 유형의 배열
  • 지원되는 모든 유형의 null 허용 버전

맞춤 Parceler

유형이 직접 지원되지 않는 경우 유형의 Parceler 매핑 객체를 작성할 수 있습니다.

class ExternalClass(val value: Int)

object ExternalClassParceler : Parceler<ExternalClass> {
    override fun create(parcel: Parcel) = ExternalClass(parcel.readInt())

    override fun ExternalClass.write(parcel: Parcel, flags: Int) {
        parcel.writeInt(value)
    }
}

다음과 같이 @TypeParceler 또는 @WriteWith 주석을 사용하여 외부 parceler를 적용할 수 있습니다.

// Class-local parceler
@Parcelize
@TypeParceler<ExternalClass, ExternalClassParceler>()
class MyClass(val external: ExternalClass) : Parcelable

// Property-local parceler
@Parcelize
class MyClass(@TypeParceler<ExternalClass, ExternalClassParceler>() val external: ExternalClass) : Parcelable

// Type-local parceler
@Parcelize
class MyClass(val external: @WriteWith<ExternalClassParceler>() ExternalClass) : Parcelable

Parcel에서 데이터 만들기

Java 코드에서는 CREATOR 필드에 직접 액세스할 수 있습니다.

class UserCreator {
    static User fromParcel(Parcel parcel) {
        return User.CREATOR.createFromParcel(parcel);
    }
}

Kotlin에서는 CREATOR 필드를 직접 사용할 수 없습니다. 대신 kotlinx.parcelize.parcelableCreator를 사용하세요.

import kotlinx.parcelize.parcelableCreator

fun userFromParcel(parcel: Parcel): User {
    return parcelableCreator<User>().createFromParcel(parcel)
}

직렬화에서 속성 건너뛰기

일부 속성이 parceling되지 않도록 건너뛰려면 @IgnoredOnParcel 주석을 사용하세요. 클래스 본문 내 속성에 사용하여 속성이 직렬화되지 않는 것에 관한 경고를 표시하지 않을 수도 있습니다. @IgnoredOnParcel로 주석 처리된 생성자 속성에는 기본값이 있어야 합니다.

@Parcelize
class MyClass(
    val include: String,
    // Don't serialize this property
    @IgnoredOnParcel val ignore: String = "default"
): Parcelable {
    // Silence a warning
    @IgnoredOnParcel
    val computed: String = include + ignore
}

속성 직렬화에 android.os.Parcel.writeValue 사용

@RawValue를 사용하여 유형에 주석을 달면 Parcelize가 해당 속성에 Parcel.writeValue를 사용하도록 할 수 있습니다.

@Parcelize
class MyClass(val external: @RawValue ExternalClass): Parcelable

속성 값이 Android에서 기본적으로 지원되지 않는 경우 런타임에 실패할 수 있습니다.

Parcelize에서 속성을 직렬화하는 다른 방법이 없는 경우 이 주석을 사용해야 할 수도 있습니다.

봉인된 클래스 및 봉인된 인터페이스로 파티션화

Parcelize를 사용하려면 클래스가 추상 클래스가 아니어야 합니다. 이 제한은 봉인 클래스에는 유지되지 않습니다. @Parcelize 주석이 봉인된 클래스에서 사용되면 파생 클래스에서 반복할 필요가 없습니다.

@Parcelize
sealed class SealedClass: Parcelable {
    class A(val a: String): SealedClass()
    class B(val b: Int): SealedClass()
}

@Parcelize
class MyClass(val a: SealedClass.A, val b: SealedClass.B, val c: SealedClass): Parcelable

Kotlin 멀티플랫폼용 Parcelize 설정

Kotlin 2.0 이전에는 Parcelize 주석을 expectactual로 별칭 지정하여 Parcelize를 사용할 수 있었습니다.

// Common code
package example

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
expect annotation class MyParcelize()

expect interface MyParcelable

@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.SOURCE)
expect annotation class MyIgnoredOnParcel()

@MyParcelize
class MyClass(
    val x: String,
    @MyIgnoredOnParcel val y: String = ""
): MyParcelable

// Platform code
package example

actual typealias MyParcelize = kotlinx.parcelize.Parcelize
actual typealias MyParcelable = android.os.Parcelable
actual typealias MyIgnoredOnParcel = kotlinx.parcelize.IgnoredOnParcel

Kotlin 2.0 이상에서는 플러그인을 트리거하는 별칭 지정 주석이 지원되지 않습니다. 이를 우회하려면 대신 새 Parcelize 주석을 플러그인에 additionalAnnotation 매개변수로 제공합니다.

// Gradle build configuration
kotlin {
    androidTarget {
        compilerOptions {
            // ...
            freeCompilerArgs.addAll("-P", "plugin:org.jetbrains.kotlin.parcelize:additionalAnnotation=example.MyParcelize")
        }
    }
}
// Common code
package example

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
// No `expect` keyword here
annotation class MyParcelize()

expect interface MyParcelable

@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.SOURCE)
expect annotation class MyIgnoredOnParcel()

@MyParcelize
class MyClass(
    val x: String,
    @MyIgnoredOnParcel val y: String = ""
): MyParcelable

// Platform code
package example

// No typealias for MyParcelize here
actual typealias MyParcelable = android.os.Parcelable
actual typealias MyIgnoredOnParcel = kotlinx.parcelize.IgnoredOnParcel

Parcel 인터페이스는 Android에서만 사용할 수 있으므로 Parcelize는 다른 플랫폼에서 코드를 생성하지 않으므로 모든 actual 구현이 비어 있을 수 있습니다. 또한 공통 코드에서 Parcel 클래스를 참조해야 하는 주석(예: @WriteWith)을 사용할 수 없습니다.

실험용 기능

데이터 클래스 직렬기

Kotlin 2.1.0부터 사용할 수 있습니다.

DataClass 주석을 사용하면 데이터 클래스가 마치 Parcelize 주석이 달린 것처럼 직렬화할 수 있습니다. 이 주석을 사용하려면 kotlinx.parcelize.Experimental를 선택해야 합니다.

@file:OptIn(kotlinx.parcelize.Experimental::class)

data class C(val a: Int, val b: String)

@Parcelize
class P(val c: @DataClass C) : Parcelable

기본 생성자와 기본 생성자의 모든 속성은 Parcelable 클래스에서 액세스할 수 있어야 합니다. 또한 데이터 클래스의 모든 기본 생성자 속성을 Parcelize에서 지원해야 합니다. 선택한 맞춤 Parceler는 데이터 클래스가 아닌 Parcelable 클래스에 지정해야 합니다. 데이터 클래스가 동시에 Serializable를 구현하는 경우 @DataClass 주석이 우선 적용되며 android.os.Parcel.writeSerializable는 사용되지 않습니다.

이에 대한 실용적인 사용 사례는 kotlin.Pair 직렬화입니다. 또 다른 유용한 예는 멀티플랫폼 코드를 단순화하는 것입니다. 공통 코드에서 데이터 영역을 데이터 클래스로 선언하면 Android 코드에서 직렬화 로직으로 이를 보강할 수 있으므로 공통 코드에서 Android별 주석과 유형 별칭이 필요하지 않게 됩니다.

// Common code:
data class MyData(val x: String, val y: MoreData)
data class MoreData(val a: String, val b: Int)

// Platform code:
@OptIn(kotlinx.parcelize.Experimental::class)
@Parcelize
class DataWrapper(val wrapped: @DataClass MyData): Parcelable

기본 생성자의 val 또는 var가 아닌 매개변수

Kotlin 2.1.0부터 사용할 수 있습니다.

이 기능을 사용 설정하려면 parcelize 플러그인 인수에 experimentalCodeGeneration=true를 추가하세요.

kotlin {
    compilerOptions {
        // ...
        freeCompilerArgs.addAll("-P", "plugin:org.jetbrains.kotlin.parcelize:experimentalCodeGeneration=true")
    }
}

이 기능은 기본 생성자 인수가 val 또는 var이어야 한다는 제한을 삭제합니다. 이렇게 하면 이전에 open 속성을 사용해야 했던 상속이 포함된 parcelize를 사용할 때의 한 가지 고충이 해결됩니다.

// base parcelize
@Parcelize
open class Base(open val s: String): Parcelable

@Parcelize
class Derived(
    val x: Int,
    // all arguments have to be `val` or `var` so we need to override
    // to not introduce new property name
    override val s: String
): Base(s)

// experimental code generation enabled
@Parcelize
open class Base(val s: String): Parcelable

@Parcelize
class Derived(val x: Int, s: String): Base(s)

이러한 매개변수는 기본 클래스 생성자의 인수에서만 사용할 수 있습니다. 클래스 본문에서 참조하는 것은 허용되지 않습니다.

@Parcelize
class Derived(s: String): Base(s) { // allowed
    @IgnoredOnParcel
    val x: String = s // ERROR: not allowed.
    init {
        println(s) // ERROR: not allowed
    }
}

의견

kotlin-parcelize Gradle 플러그인에서 문제가 발생하면 버그를 신고해 주세요.