@Parcelize

kotlin-parcelize 플러그인은 Parcelable 구현을 자동으로 해준다.

새로운 클래스를 생성도 필요 없고 @Parcelize 어노테이션을 추가하는 것만으로 직접 Parcelable 관련 코드를 작성 한 것과 같이 동작한다. 컴파일타임에 바이트 코드 변조를 하기 때문에 추가되는 메서드 및 런타임시 오버헤드 비용도 발생하지 않는다. 그리고 무엇보다 이 플러그인은 구글과 JetBrains가 협업하여 만든 플러그인이기 때문에 다른 3rd-party 라이브러리와는 다르게 추후 계속 유지보수 될 것이라 기대한다. 

Parcelable에 대해서는 이전 포스팅에서 확인할 수 있다.

프로젝트 설정

기본적으로 코틀린(1.4.20기준)에서만 사용할 수 있기 때문에 다음과 같은 프로젝트 설정이 되어 있어야 한다.

build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'

기본적인 사용 방법

다음과 같이 Parcelable을 구현해야 할 대상 클래스에 @Parcelize 어노테이션만 추가하면 모든 설정이 끝난다.

import kotlinx.parcelize

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

주요 제약사항은 @Parcelize를 사용하려면 직렬화 된 모든 속성이 기본 생성자(Primary constructor)에 선언되어야 하며, abstract 또는 sealed 클래스를 허용하지 않는 다는 점이다. 

@Parcelize가 지원하는 타입

  • 원시타입 그리고 원시타입의 박스타입 지원
  • Object와 enum 지원
  • String, CharSequence 지원
  • Exception 지원
  • Size, SizeF, Bundle, IBinder, IInterface, FileDescriptor 지원
  • SparseArray, SparseIntArray, SparseLongArray, SparseBooleanArray 지원 
  • 모든 Serializable(Date 포함) 그리고 Parcelable 구현체들 지원
  • 모든 타입의 Collection 지원
    List는 ArrayList로 매핑, Set은 LinkedHashSet으로 매핑, Map은 LinkedHashMap으로 매핑
  • 모든 타입의 배열 지원
  • 지원하는 모든 타입의 Nullable 버전 지원

커스텀 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)
    }
}

외부에서 Parceler를 만들 수 있도록 @TypeParceler 또는 @WriteWith 어노테이션을 적용하면 되는데, 기본적인 용법은 다음과 같다.

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

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

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

Custom Parceler 사용 예제

다음과 같이 Date 타입을 Parceling 하는 외부 Parceler가 있다고 가정한다.

object DateParceler : Parceler<Date> {

    override fun create(parcel: Parcel) = Date(parcel.readLong())

    override fun Date.write(parcel: Parcel, flags: Int)
            = parcel.writeLong(time)
}

@TypeParceler 사용하기

같은 타입(Date)의 속성이 여러개 선언 되어 있다면 반복적인 작업을 피하기 위해 @TypeParceler<T, P>를 사용할 수 있다. 

제너릭 T에는 Parcelable 대상 타입을 명시하고, 제너릭 P에는 외부 Parceler 타입을 명시한다.

@Parcelize
@TypeParceler<Date, DateParceler>
class Session(
    val title: String,
    val startTime: Date,
    val endTime: Date
): Parcelable

또는 다음과 같이 사용할 수도 있다.

@Parcelize
class Session(
    val title: String,
    @TypeParceler<Date, DateParceler> val startTime: Date,
    @TypeParceler<Date, DateParceler> val endTime: Date
): Parcelable

@WriteWith 사용하기

특정 속성에 대한 외부 Parceler를 명시적으로 지정하여 어떤 구현체가 직렬화에 사용할지 정하고, 애매모호함을 피하고 싶다면 @WriteWith를 사용할 수 있다.

@Parcelize
class Session(
    val title: String,
    val startTime: @WriteWith<DateParceler> Date,
    val endTime: @WriteWith<DateParceler> Date
) : Parcelable

직렬화 대상에서 제외하기

@Parcelize 사용시 멤버 속성을 제외시키고 싶다면 @IgnoredOnParcel 를 사용하자

@Parcelize
class Book(val title: String, val totalPages: Int) : Parcelable {
    @IgnoredOnParcel
    var readPages: Int = 0
}

Conclusion

좀 더 자세한 내용을 알고 싶다면 이 아티클을 참조하자. Parcelize 기능에 대한 완벽하게 설명하고 있다고 생각한다. 내부에서 바이트코드가 어떤식으로 생성되는지에 대한 내용도 나와있는데, 이를 한번 참조해보면 @Parcelize를 사용할 때 생기는 문제점을 유연하게 대처할 수 있다.

카테고리: 미분류

5개의 댓글

변성욱 · 2021년 6월 9일 9:48 오전

멋진 포스트입니다!

Charlezz · 2021년 6월 10일 12:44 오후

멋진 변성욱님 감사합니다

이현주 · 2021년 9월 26일 5:41 오후

오늘도 많이 배워갑니다 감사합니다!!

    Charlezz · 2021년 9월 28일 10:06 오전

    많이 배워가신다니 뿌듯합니다 🙂

성빈 · 2022년 5월 30일 9:55 오전

멋진 포스팅 감사합니다 🙂

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.