@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 오전
멋진 포스팅 감사합니다 🙂