

Use Parceler to put your parcels on a diet – le0nidas
source link: https://le0nidas.gr/2022/03/20/use-parceler-to-put-your-parcels-on-a-diet/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Use Parceler to put your parcels on a diet
kotlin-parcelize is a great tool. Its simple to use and it helps in avoiding writing a lot of boilerplate code. There are times though that we need to take control of writing and reading to/from the parcel. One of these times is to cut down a few bytes from it (TransactionTooLargeException I am looking at you).
Meet me in the middle
@Parcelize
takes full control and creates everything. Without the annotation, the developer has to do this on her own. Parceler
lives in the middle of this spectrum. The plugin will create all necessary methods and classes but the actual write and read to/from the parcel will be the developer’s responsibility.
Without a Parceler
the write/read looks like this:
public void writeToParcel(@NotNull Parcel parcel, int flags) { Intrinsics.checkNotNullParameter(parcel, "parcel"); parcel.writeInt(this.id); parcel.writeString(this.description); parcel.writeString(this.priority.name()); parcel.writeParcelable(this.status, flags); Attachment var10001 = this.attachment; if (var10001 != null) { parcel.writeInt(1); var10001.writeToParcel(parcel, 0); } else { parcel.writeInt(0); } }
@NotNull public final Task createFromParcel(@NotNull Parcel in) { Intrinsics.checkNotNullParameter(in, "in"); return new Task( in.readInt(), in.readString(), (Priority)Enum.valueOf(Priority.class, in.readString()), (Status)in.readParcelable(Task.class.getClassLoader()), in.readInt() != 0 ? (Attachment)Attachment.CREATOR.createFromParcel(in) : null ); }
with a Parceler
like this (where the Companion
object is acting as a Parceler
):
public void writeToParcel(@NotNull Parcel parcel, int flags) { Intrinsics.checkNotNullParameter(parcel, "parcel"); Companion.write(this, parcel, flags); }
@NotNull public final Task createFromParcel(@NotNull Parcel in) { Intrinsics.checkNotNullParameter(in, "in"); return Task.Companion.create(in); }
Cutting down parcel’s size
The above-generated code is based on Task
@Parcelize class Task( val id: Int, val description: Description, val priority: Priority = Normal, val status: Status = NotStarted, val attachment: Attachment? = null ) : Parcelable
@Parcelize class Attachment(val path: String) : Parcelable
@Parcelize @JvmInline value class Description(val value: String) : Parcelable
enum class Priority { Low, Normal, High }
sealed class Status : Parcelable { @Parcelize object NotStarted : Status()
@Parcelize object InProgress : Status()
@Parcelize class Completed(val completedAt: LocalDate) : Status() }
which, creates a parcel of 248 bytes. The code does not do anything weird. All primitives, which include the value classes too, are well handled. So nothing to do here. This leaves parcelables and enums.
But first, let’s use a Parceler. This means that writing and reading to/from the parcel has to be implemented by us. For starters, we will do exactly what the generated code does except for the attachment
property. For that, the generated code uses parcelable’s methods and CREATOR
. In the Parceler
we don’t have access to the CREATOR
.
companion object : Parceler<Task> { override fun create(parcel: Parcel): Task { return Task( parcel.readInt(), Description(parcel.readString()!!), Priority.valueOf(parcel.readString()!!), parcel.readParcelable(Status::class.java.classLoader)!!, parcel.readParcelable(Attachment::class.java.classLoader) ) }
override fun Task.write(parcel: Parcel, flags: Int) { with(parcel) { writeInt(id) writeString(description.value) writeString(priority.name) writeParcelable(status, flags) writeParcelable(attachment, flags) } } }
That leaves us with writeParcelable
and readParcelable
but now the parcel’s size is bigger, it is 328 bytes! Turns out that writeParcelable
first writes the parcelable’s name and then the parcelable itself!
We need to use the CREATOR. After searching around I found parcelableCreator
. A function that solved a well-known problem and will be added to Kotlin 1.6.20.
inline fun <reified T : Parcelable> Parcel.readParcelable(): T? { val exists = readInt() == 1 if (!exists) return null return parcelableCreator<T>().createFromParcel(this) }
@Suppress("UNCHECKED_CAST") inline fun <reified T : Parcelable> parcelableCreator(): Parcelable.Creator<T> = T::class.java.getDeclaredField("CREATOR").get(null) as? Parcelable.Creator<T> ?: throw IllegalArgumentException("Could not access CREATOR field in class ${T::class.simpleName}")
fun <T : Parcelable> Parcel.writeParcelable(t: T?) { if (t == null) { writeInt(0) } else { writeInt(1) t.writeToParcel(this, 0) } }
This allows us to revert the size increment back to 248 bytes
companion object : Parceler<Task> { override fun create(parcel: Parcel): Task { return Task( //… parcel.readParcelable() ) }
override fun Task.write(parcel: Parcel, flags: Int) { with(parcel) { //… writeParcelable(attachment) } } }
Use enum’s ordinal than its name. The generated code writes enum’s name so that it can use Enum.valueOf
when reading. We can write an int instead by using enum’s ordinal
companion object : Parceler<Task> { override fun create(parcel: Parcel): Task { return Task( //… parcel.readEnum() ) }
override fun Task.write(parcel: Parcel, flags: Int) { with(parcel) { //… writeEnum(priority) } } }
inline fun <reified T : Enum<T>> Parcel.readEnum(): T { return enumValues<T>()[readInt()] }
inline fun <reified T : Enum<T>> Parcel.writeEnum(t: T) { writeInt(t.ordinal) }
and use Enum.values()
when reading. This drops the parcel’s size to 232 bytes.
Skip a class’s parcelable implementation. This of course depends on each implementation.
For instance, Status
is a sealed class that only one of its children has a construction parameter. We can leverage this by writing only that value
companion object : Parceler<Task> {
override fun create(parcel: Parcel): Task { return Task( //… parcel.readStatus() ) }
override fun Task.write(parcel: Parcel, flags: Int) {
with(parcel) { //… writeStatus(status) } } }
fun Parcel.readStatus(): Status { return readLong().let { value -> when (value) { 0L -> NotStarted 1L -> InProgress else -> Completed(LocalDate.ofEpochDay(value)) } } }
fun Parcel.writeStatus(status: Status) { when (status) { is Completed -> writeLong(status.completedAt.toEpochDay()) InProgress -> writeLong(1) NotStarted -> writeLong(0) } }
this drops the parcel’s size to 136 bytes!
Conclusion
Fortunately, the generated code does a pretty good job and making any optimizations is not that common. But when needed Parceler
and parcelableCreator
are great tools.
PS: for measuring the parcel’s size I was using this method
fun Parcelable.sizeInBytes(): Int { val parcel = Parcel.obtain() try { parcel.writeParcelable(this, 0) return parcel.dataSize() } finally { parcel.recycle() } }
which was shamelessly stolen from Guardian’s TooLargeTool.
Related
Lets build a coroutineFebruary 23, 2021In "Kotlin"
The memento design pattern in KotlinJanuary 7, 2021In "Design Patterns"
Introduction to GitHub ActionsAugust 29, 2020In "Introduction"
Recommend
-
54
New ASP.NET Core projects use an omnibus package called the Microsoft.AspNetCore.App . Also known as the “ASP.NET Core Shared Framework”, the basic...
-
9
如何自定义编译zeppelin的parcels与CDH集成标签(空格分隔):大数据运维专栏一:关于zeppelin介绍二:如何自定义CDH的parcels与csd的jar包三:zeppelin与CDH的集成四:关于zeppelin的测试一:关于zepplin的介绍ApacheZeppelin是一个让交互式数据分析变得可行的基...
-
11
Your Media Diet Will Never Be the SameWe have a glut of streaming options. But if recent events show us anything, it’s that live TV news is far from obsolete.The Netflix show Bridgerto...
-
20
Combining parcels in Mathematica does not give the expected result advertisements I'm trying to combine 3 functions graphed on a Plot[]
-
6
news UPS is testing a new electric mini van for delivering parcels in the US...
-
9
Evri couriers: Wiltshire customers report parcels going missingPublished15 hours ago
-
11
Royal Mail overseas parcels ban 'costing me hundreds of pounds'Published54 minutes ago
-
6
You don’t have to freak out about aspartame in your diet soda / The IARC will reportedly classify aspartame as a possible carcinogen. But this isn’t a food safety agency, and the context matters.
-
8
DELIVERY UX•7 min read•ListenThe UX of delivering parcels
-
7
Tuesday, 19 December 2023 12:48 Australia Post to pay about $2.9 million in compensation to businesses for lost or damaged parcels Featured By Gordon Peters
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK