01 November 2017

ภาษา Kotlin กับการทำ Parcelable ในแอนดรอยด์

Updated on

        จะว่าไปนี่เป็นบทความแรกเลยนะเนี่ยที่พูดถึง Kotlin โดยเฉพาะ ซึ่งเกิดมาจากการที่เจ้าของบล็อกได้ใช้ภาษา Kotlin ในการเขียนแอปฯแอนดรอยด์แทนภาษา Java แล้วก็พบว่ามีหลายๆอย่างที่ยังไม่ค่อยมีข้อมูลซักเท่าไรนักว่าเรียกใช้คำสั่งยังไงดีเมื่อต้องมาใช้ในแอนดรอยด์ ซึ่งหนึ่งในนั้นก็คือ Parcelable นั่นเอง

นักพัฒนาแอนดรอยด์ทุกคนรู้จัก Parcelable อยู่แล้วเนอะ?

        นักพัฒนาแอนดรอยด์ส่วนใหญ่รู้กันอยู่แล้วว่า Parcelable เป็นการทำให้ Model Class ใดๆก็ตามให้อยู่ในรูปของ "พัสดุ" ที่จะถูกส่งไปมาระหว่างคลาสต่างๆของแอนดรอยด์ตามที่ทีมพัฒนาได้ออกแบบไว้

        สมมติว่าเจ้าของบล็อกมี Model Class ที่มีหน้าตาแบบนี้

import android.os.Parcel;
import android.os.Parcelable;

public class Profile implements Parcelable {
    String name;
    String job;
    int age;

    ...
}

        และเมื่ออยากจะทำให้มันเป็น Parcelable ก็เพิ่มโค้ดเข้าไปแบบนี้

import android.os.Parcel;
import android.os.Parcelable;

public class Profile implements Parcelable {
    String name;
    String job;
    int age;

    public Profile() {
    }

    ...

    protected Profile(Parcel in) {
        name = in.readString();
        job = in.readString();
        age = in.readInt();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(name);
        parcel.writeString(job);
        parcel.writeInt(age);
    }

    public static final Creator<Profile> CREATOR = new Creator<Profile>() {
        @Override
        public Profile createFromParcel(Parcel in) {
            return new Profile(in);
        }

        @Override
        public Profile[] newArray(int size) {
            return new Profile[size];
        }
    };
}

แต่ถ้าเป็น Kotlin จะต้องเขียนยังไงล่ะ?

        วิธีง่ายที่สุดของการเขียน Kotlin บนแอนดรอยด์ก็คือการเขียนออกมาเป็น Java ก่อน แล้วสร้างคลาสสำหรับ Kotlin ขึ้นมา แล้วแปะโค้ด Java ลงไปซะเลย! เดี๋ยวมันก็แปลงออกมาเป็น Kotlin ให้อัตโนมัติ ก็เลยได้ออกมาเป็นแบบนี้

import android.os.Parcel
import android.os.Parcelable

class Profile protected constructor(`in`: Parcel) : Parcelable {
    internal var name: String
    internal var job: String
    internal var age: Int = 0

    init {
        name = `in`.readString()
        job = `in`.readString()
        age = `in`.readInt()
    }

    override fun describeContents(): Int {
        return 0
    }

    override fun writeToParcel(parcel: Parcel, i: Int) {
        parcel.writeString(name)
        parcel.writeString(job)
        parcel.writeInt(age)
    }

    companion object {
        val CREATOR: Parcelable.Creator<Profile> = object : Parcelable.Creator<Profile> {
            override fun createFromParcel(`in`: Parcel): Profile {
                return Profile(`in`)
            }

            override fun newArray(size: Int): Array<Profile> {
                return arrayOfNulls(size)
            }
        }
    }
}

        แต่ก็อย่างที่รู้กันแหละว่า Auto Convert ของ Kotlin มันไม่ได้สวยหรูขนาดนั้น และโค้ดข้างบนนี้ก็ไม่ถูกต้องเช่นกัน แถมทำงานไม่ได้ด้วย

        ทีนี้มาดูกันว่าจริงๆแล้ว Model Class ที่เป็น Parcelable เวลาเขียนเป็น Kotlin จะเป็นยังไง

สร้างคลาสให้เป็น Kotlin แต่อย่าเพิ่งใส่ Parcelable เข้าไป

        เพื่อไม่ให้โค้ดมันเปลี่ยนปุปปับจนดูไม่รู้เรื่อง ให้เริ่มจากสร้าง Model Class แบบ Kotlin ก่อนครับ

data class Profile(var name: String, var job: String, var age: Int) {

}

        เท่ป่ะล่ะ โค้ดเหลือแค่บรรทัดเดียวด้วย Kotlin (ขายของๆ)

        ต่อมาก็ให้ Extends จาก Parcelable แล้วใส่คำสั่งที่จำเป็นสำหรับ Parcelable เข้าไปดังนี้

import android.os.Parcel
import android.os.Parcelable

data class Profile(var name: String, var job: String, var age: Int) : Parcelable {

    constructor(parcel: Parcel) : this(
            parcel.readString(),
            parcel.readString(),
            parcel.readInt())

    override fun writeToParcel(parcel: Parcel, flags: Int) {

    }

    override fun describeContents() = 0

    companion object {
        @JvmField
        val CREATOR = object : Parcelable.Creator<Profile> {
            override fun createFromParcel(parcel: Parcel) = Profile(parcel)

            override fun newArray(size: Int) = arrayOfNulls<Profile>(size)
        }
    }
}

        ให้สังเกตที่ constructor(parcel: Parcel)  ดีๆ มันคือ Constructor ของ Parcelable นั่นเอง ซึ่งจะต้องกำหนดค่าตาม Constructor ที่เจ้าของบล็อกสร้างไว้ตอนแรกเลย โดยค่าแต่ละตัวจะต้องดึงมาจาก Parcel

// Constructor ที่สร้างไว้
data class Profile(var name: String, var job: String, var age: Int)

// Consturctor ที่จะต้องสร้างสำหรับ Parcelable
constructor(parcel: Parcel) : this(parcel.readString(), parcel.readString(), parcel.readInt())

        ซึ่งความสัมพันธ์ของ Constructor ทั้ง 2 จะสัมพันธ์กันแบบนี้

        • name เป็น String ก็เลยต้องใช้ parcel.readString()
        • job เป็น String ก็เลยต้องใช้ parcel.readString()
        • age เป็น Integer ก็เลยต้องใช้ parcel.readInt()

        ดังนั้นขึ้นอยู่กับว่าคลาสนั้นๆกำหนด Properties อะไรไว้ในคลาสนี้บ้าง ก็กำหนดให้ตรงกันซะ และถ้าอยากให้ดูง่ายขึ้นและไม่สับสน เจ้าของบล็อกแนะนำให้เขียนแบบนี้แทน

// จะเขียนแบบนี้
constructor(parcel: Parcel) : this(
            parcel.readString(),
            parcel.readString(),
            parcel.readInt())

// หรือแบบนี้ก็ได้
constructor(parcel: Parcel) : this(
            name = parcel.readString(),
            job = parcel.readString(),
            age = parcel.readInt())

        จริงๆก็ใช้ได้ทั้งคู่ แต่อยู่ที่ว่าชอบแบบไหนมากกว่ากัน

        ต่อมาให้ดูในคำสั่ง writeToParcel(parcel: Parcel, flags: Int) ในนี้จะต้องเก็บค่าจาก Properties ทั้งหมดมาเก็บไว้ใน Parcel

override fun writeToParcel(parcel: Parcel, flags: Int) {
    parcel.writeString(name)
    parcel.writeString(job)
    parcel.writeInt(age)
}

// แต่ถ้าอยากให้สวยงามตามแบบฉบับ Kotlin ให้เขียนแบบนี้ดีกว่า
override fun writeToParcel(parcel: Parcel, flags: Int) {
    with(parcel) {
        writeString(name)
        writeString(job)
        writeInt(age)
    }
}

        คำเตือน : ลำดับของคำสั่งใน writeToParcel(...) จะต้องเรียงตามลำดับของ Properties ให้ตรงกับใน Constructor ด้วย ห้ามสลับลำดับเด็ดขาด

        ส่วน describeContents() กับ CREATOR ไม่ต้องสนใจอะไร ตรงนี้เป็น Boilerplate ที่ต้องเขียนทุกครั้ง

        ดังนั้นคลาสตัวอย่างนี้เมื่อทำเป็น Parcelable ด้วย Kotlin จะได้ออกมาเป็นแบบนี้

import android.os.Parcel
import android.os.Parcelable

data class Profile(var name: String, var job: String, var age: Int) : Parcelable {

    constructor(parcel: Parcel) : this(
            name = parcel.readString(),
            job = parcel.readString(),
            age = parcel.readInt())

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        with(parcel) {
            writeString(name)
            writeString(job)
            writeInt(age)
        }
    }

    override fun describeContents() = 0

    companion object {
        @JvmField
        val CREATOR = object : Parcelable.Creator<Profile> {
            override fun createFromParcel(parcel: Parcel) = Profile(parcel)

            override fun newArray(size: Int) = arrayOfNulls<Profile>(size)
        }
    }
}

        ข้อควรระวัง : คลาสใน CREATOR นั้นจะต้องตรงกับชื่อคลาสนะ

data class Profile : Parcelable {
    ...    
    @JvmField
    val CREATOR = object : Parcelable.Creator<Profile> {
        override fun createFromParcel(parcel: Parcel): Profile = Profile(parcel)

        override fun newArray(size: Int): Array<Profile?> = arrayOfNulls(size)
    }
}


ถ้าต้องสร้างคลาสที่ Extend จากคลาสที่เป็น Parcelable (Hierarchies of Parcelable classes) ล่ะ?

        ลำพังการสร้างคลาสให้เป็น Parcelable ไม่ได้มีปัญหาอะไรซักเท่าไร แต่ถ้ามีคลาสอีกตัวที่มีคลาสตัวนี้เป็น Super Class ล่ะ? จะต้องเขียนยังไงต่อ?

        ย้อนกลับมาดูแบบ Java ก่อน สมมติว่าเจ้าของบล็อกมีคลาสอีกตัวที่จะ Extend และอยากให้เป็น Parcelable เหมือนกันก็แค่ Extends มาแล้วใส่โค้ดสำหรับ Parcelable ให้เรียบร้อยซะ

import android.os.Parcel;
import android.os.Parcelable;

public class AwesomeProfile extends Profile {
    boolean isAwesome;
    int reputation;

    public AwesomeProfile() {
        super();
    }

    ...

    protected AwesomeProfile(Parcel in) {
        super(in);
        isAwesome = in.readByte() != 0;
        reputation = in.readInt();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int flags) {
        super.writeToParcel(parcel, flags)
        parcel.writeByte((byte) (isAwesome ? 1 : 0));
        parcel.writeInt(reputation);
    }

    public static final Creator<AwesomeProfile> CREATOR = new Creator<AwesomeProfile>() {
        @Override
        public AwesomeProfile createFromParcel(Parcel in) {
            return new AwesomeProfile(in);
        }

        @Override
        public AwesomeProfile[] newArray(int size) {
            return new AwesomeProfile[size];
        }
    };
}

        แล้วถ้าเป็น Kotlin ล่ะ?

        สิ่งที่ต้องทำก็จะคล้ายๆกันครับ แต่ทว่ารูปแบบการเขียนจะเปลี่ยนไปเป้นรูปแบบของ Kotlin แทน โดยเริ่มจากเปลี่ยนรูปแบบของ Constructure เพื่อให้ง่ายต่อการนำไปใช้เป็น Super Class

import android.os.Parcel
import android.os.Parcelable

open class Profile : Parcelable {
    lateinit var name: String
    lateinit var job: String
    var age: Int = 0

    constructor()

    constructor(parcel: Parcel) {
        name = parcel.readString()
        job = parcel.readString()
        age = parcel.readInt()
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        with(parcel) {
            writeString(name)
            writeString(job)
            writeInt(age)
        }
    }

    override fun describeContents() = 0

    companion object {
        @JvmField
        val CREATOR = object : Parcelable.Creator {
            override fun createFromParcel(parcel: Parcel) = Profile(parcel)

            override fun newArray(size: Int) = arrayOfNulls(size)
        }
    }
}

        ในตัวอย่างข้างบนนี้จะไม่ได้กำหนด Properties ต่างๆใน Constructor แต่ก็สามารถเพิ่ม Constructor ได้เองตามใจชอบ

        ส่วนคลาสที่จะมา Extend ก็ให้ทำในลักษณะคล้ายกัน

import android.os.Parcel
import android.os.Parcelable

data class AwesomeProfile : Profile {
    var isAwesome: Boolean = false
    var reputation: Int = 0

    constructor() : super()

    ...
}

        แล้วเพิ่มคำสั่งสำหรับ Parcelable เข้าไปดังนี้

import android.os.Parcel
import android.os.Parcelable

data class AwesomeProfile : Profile {
    var isAwesome: Boolean = false
    var reputation: Int = 0

    constructor() : super()

    constructor(parcel: Parcel) : super(parcel) {
        isAwesome = parcel.readInt() != 0
        reputation = parcel.readInt()
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        super.writeToParcel(parcel, flags)
        with(parcel) {
            writeInt(if (isAwesome) 1 else 0)
            writeInt(reputation)
        }
    }

    override fun describeContents(): Int = 0

    companion object {
        @JvmField
        val CREATOR = object : Parcelable.Creator<AwesomeProfile> {
            override fun createFromParcel(parcel: Parcel): AwesomeProfile = AwesomeProfile(parcel)

            override fun newArray(size: Int): Array<AwesomeProfile?> = arrayOfNulls(size)
        }
    }
}

        ไม่เข้าใจเหมือนกันว่าทำไมต้องลำบากเก็บค่า Boolean ลง Parcel ด้วยการแปลงเป็น Int แต่เดี๋ยวเจ้าของบล็อกจะพูดถึงทีหลัง เพื่อทำให้มันง่ายกว่านี้

        และอย่าลืมนะว่าตอนที่เก็บค่าและดึงค่าใน Parcel จะต้องเรียงลำดับตัวแปรให้เหมือนกันด้วย อย่าสลับตำแหน่งกันล่ะ

สร้างคลาสที่มี Properties ที่เป็น Parcelable 

         เพื่อให้ครอบคลุมกับการใช้งาน ลองคิดต่อว่าถ้าเจ้าของบล็อกต้องสร้างคลาสที่ข้างในมีคลาสที่เป็น Parcelable ล่ะ?

public class Location implements Parcelable {
    ...
}

public class Profile implements Parcelable {
    ...
}

public class AwesomeProfile extends Profile {
    ...
}

public class Company implements Parcelable {
    List<AwesomeProfile> profileList;
    Location location;
    ...
}

        แล้วเจ้าของบล็อกจะต้องจัดการกับ Properties เหล่านี้ยังไง?

        ก่อนอื่นให้สร้างคลาสขึ้นมาเป็น Kotlin แต่อย่างเพิ่งทำเป็น Parcelable เหมือนเดิมฮะ

// จะเขียนแบบนี้
class Company {
    lateinit var profile: List<AwesomeProfile>
    lateinit var location: Location

    constructor() : super()

    ...
}

// หรือแบบนี้ก็ได้
class Company(var profile: List, var location: Location) : Parcelable {
    ...
}

        จากนั้นก็ทำให้มันกลายเป็น Parcelable ซะ ซึ่งขึ้นอยู่กับว่าเขียน Constructor ไว้แบบไหนครับ

        ถ้าเป็นแบบแรกที่ประกาศ Properties ไว้เป็น Global Variable ใน Constructor สำหรับ Parcel จะต้องเขียนแบบนี้

class Company : Parcelable {
    lateinit var profile: List<AwesomeProfile>
    lateinit var location: Location

    constructor() : super()

    constructor(parcel: Parcel) {
        profile = mutableListOf<AwesomeProfile>().apply {
            parcel.readTypedList(this, AwesomeProfile.CREATOR)
        }
        location = parcel.readParcelable(Location::class.java.classLoader)
    }
    ...
}

        ถ้าเป็นแบบ Primary Constructor เลยก็จะเขียนแบบนี้แทน

class Company(var profile: List<AwesomeProfile>, var location: Location) : Parcelable {
    constructor(parcel: Parcel) : this(
            profile = mutableListOf<AwesomeProfile>().apply {
                parcel.readTypedList(this, AwesomeProfile.CREATOR)
            },
            location = parcel.readParcelable(Location::class.java.classLoader)
    )
    ...
}

        ซึ่งจะใช้แบบไหนก็ขึ้นอยู่กับว่าโครงสร้างคลาสนั้นๆเป็นยังไง แต่ในกรณีนี้ รูปแบบที่สองจะได้โค้ดที่กระชับกว่า และจะใช้รูปแบบนี้ในการอธิบายต่อนะ

        ที่แตกต่างกันก็มีแค่ตรง Constructor เท่านั้นแหละครับ แต่ส่วนที่เป็นคำสั่งที่เหลือของ Parcelable ก็จะเหมือนกันๆ โดยคลาสที่เป็น Parcelable ไม่ว่าจะเป็นตัวเดียวหรือเป็น List เวลาเก็บหรือดึงข้อมูลจาก Parcel จะใช้คำสั่งในรูปแบบนี้

var location: Location
var profile: List<AwesomeProfile>

//////////////////////////
// Write Parcel
//////////////////////////

// List
profile = mutableListOf<AwesomeProfile>().apply {
    parcel.readTypedList(this, AwesomeProfile.CREATOR)
}

// Single
location = parcel.readParcelable(Location::class.java.classLoader)

//////////////////////////
// Read Parcel
//////////////////////////

// List
parcel.writeTypedList(profile)

// Single
parcel.writeParcelable(location, flags)

        ดังนั้นคำสั่งทั้งหมดจะได้ออกมาเป็นแบบนี้ครับ

import android.os.Parcel
import android.os.Parcelable

class Company(var profile: List<AwesomeProfile>, var location: Location) : Parcelable {
    constructor(parcel: Parcel) : this(
            profile = mutableListOf<AwesomeProfile>().apply {
                parcel.readTypedList(this, AwesomeProfile.CREATOR)
            },
            location = parcel.readParcelable(Location::class.java.classLoader)
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        with(parcel) {
            writeTypedList(profile)
            writeParcelable(location, flags)
        }
    }

    override fun describeContents(): Int = 0

    companion object {
        @JvmField
        val CREATOR = object : Parcelable.Creator<Company> {
            override fun createFromParcel(parcel: Parcel): Company = Company(parcel)

            override fun newArray(size: Int): Array<Company?> = arrayOfNulls(size)
        }
    }
}

        เพียงเท่านี้คลาสของเจ้าของบล็อกก็เป็น Parcelable และพร้อมใช้งานแล้ว

// Save
var company = ...
var bundle = Bundle()
bundle.putParcelable("extra_companry", company)

// Restore
var bundle = ...
val company = bundle.getParcelable<Company>("extra_companry")

เบื่อ Boilerplate ของ Parcelable ใช่มั้ย ทำให้มันกระทัดรัดกว่านี้ดีกว่า

        อันนี้เป็นวิธีจาก Christophe Beyls ในบทความ Reducing Parcelable boilerplate code using Kotlin ที่ใช้ความสามารถของ Kotlin เพื่อลด Boilerplate ของคำสั่งใน Parcelable ให้ลดลงครับ โดยสร้างคลาสที่ชื่อว่า KParcelable ขึ้นมาเพื่อลดโค้ดในส่วนของ describeContents() และ CREATOR รวมไปถึงสร้าง Extension เพื่อดลความยุ่งยากในคำสั่งของ Parcel อย่างเช่นกันเก็บค่า Boolean ที่ต้องแปลงเป็น Integer ก่อน

import android.os.Parcel
import android.os.Parcelable
import java.math.BigDecimal
import java.math.BigInteger
import java.util.*

interface KParcelable : Parcelable {
    override fun describeContents() = 0
    override fun writeToParcel(dest: Parcel, flags: Int)
}

// Creator factory functions
inline fun <reified T> parcelableCreator(
        crossinline create: (Parcel) -> T) =
        object : Parcelable.Creator<T> {
            override fun createFromParcel(source: Parcel) = create(source)
            override fun newArray(size: Int) = arrayOfNulls<T>(size)
        }

inline fun <reified T> parcelableClassLoaderCreator(
        crossinline create: (Parcel, ClassLoader) -> T) =
        object : Parcelable.ClassLoaderCreator<T> {
            override fun createFromParcel(source: Parcel, loader: ClassLoader) =
                    create(source, loader)

            override fun createFromParcel(source: Parcel) =
                    createFromParcel(source, T::class.java.classLoader)

            override fun newArray(size: Int) = arrayOfNulls<T>(size)
        }

// Parcel extensions

inline fun Parcel.readBoolean() = readInt() != 0

inline fun Parcel.writeBoolean(value: Boolean) = writeInt(if (value) 1 else 0)

inline fun <reified T : Enum<T>> Parcel.readEnum() =
        readInt().let { if (it >= 0) enumValues<T>()[it] else null }

inline fun <T : Enum<T>> Parcel.writeEnum(value: T?) =
        writeInt(value?.ordinal ?: -1)

inline fun <T> Parcel.readNullable(reader: () -> T) =
        if (readInt() != 0) reader() else null

inline fun <T> Parcel.writeNullable(value: T?, writer: (T) -> Unit) {
    if (value != null) {
        writeInt(1)
        writer(value)
    } else {
        writeInt(0)
    }
}

fun Parcel.readDate() =
        readNullable { Date(readLong()) }

fun Parcel.writeDate(value: Date?) =
        writeNullable(value) { writeLong(it.time) }

fun Parcel.readBigInteger() =
        readNullable { BigInteger(createByteArray()) }

fun Parcel.writeBigInteger(value: BigInteger?) =
        writeNullable(value) { writeByteArray(it.toByteArray()) }

fun Parcel.readBigDecimal() =
        readNullable { BigDecimal(BigInteger(createByteArray()), readInt()) }

fun Parcel.writeBigDecimal(value: BigDecimal?) = writeNullable(value) {
    writeByteArray(it.unscaledValue().toByteArray())
    writeInt(it.scale())
}

fun <T : Parcelable> Parcel.readTypedObjectCompat(c: Parcelable.Creator<T>) =
        readNullable { c.createFromParcel(this) }

fun <T : Parcelable> Parcel.writeTypedObjectCompat(value: T?, parcelableFlags: Int) =
        writeNullable(value) { it.writeToParcel(this, parcelableFlags) }

        จากนั้นก็เอา KParcelable ไปใช้แทน Parcelable เดิมครับ โดยขอยกตัวอย่างคลาสแบบเดิมก่อน ซึ่งจะมีหน้าตาแบบนี้

data class Location(var address: String, var zipCode: Int) : Parcelable {
    constructor(parcel: Parcel) : this(
            address = parcel.readString(),
            zipCode = parcel.readInt())

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(address)
        parcel.writeInt(zipCode)
    }

    override fun describeContents() = 0

    companion object {
        @JvmField
        val CREATOR = object : Parcelable.Creator<Location> {
            override fun createFromParcel(parcel: Parcel) = Location(parcel)

            override fun newArray(size: Int) = arrayOfNulls<Location>(size)
        }
    }
}

        เมื่อเปลี่ยนไปใช้ KParcelable ก็จะเหลือแค่นี้ครับ

data class Location(var address: String, var zipCode: Int) : KParcelable {
    constructor(parcel: Parcel) : this(
            address = parcel.readString(),
            zipCode = parcel.readInt())

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(address)
        parcel.writeInt(zipCode)
    }

    companion object {
        @JvmField
        val CREATOR = parcelableCreator(::Location)
    }
}

        รวมไปถึงตอนที่ต้องเก็บ Boolean ไว้ใน Parcel ก็ใช้คำสั่งจาก Extension ได้เลย (น้ำตาจะไหล)

var isAwesome: Boolean

// Write
parcel.writeBoolean(isAwesome)

// Read
isAwesome = parcel.readBoolean()

        และมีคำสั่งสำหรับ Object เข้ามาให้ด้วย ซึ่งเดิมทีคลาส Parcel ก็มีให้อยู่แล้วแหละ แต่ว่ารองรับกับ API 23 ขึ้นไป

var location: Location

// Write
parcel.writeTypedObjectCompat(location, flags)

// Read
location = parcel.readTypedObjectCompat(Location.CREATOR)!!

        ถ้าถามว่าใช้แบบเดิมได้มั้ย? ก็ได้แหละ แต่แบบนี้จะได้โค้ดที่สั้นลงอีกหน่อยนึง

ทำให้ชีวิตง่ายกว่านี้ด้วย @Parcelize

        การที่ Kotlin ถูกประกาศเป็น First-class citizen ก็ไม่ได้หมายความว่าแค่ใช้ Kotlin ได้เท่านั้นนะ แต่จะรวมไปถึงการอัพเดทเพื่อรองรับการทำงานบนแอนดรอยด์ให้สะดวกกว่าเดิมด้วย Kotlin ซึ่งหนึ่งในนั้นก็คือฟีเจอร์ Parcelize ที่ถูกเพิ่มเข้ามาใน Kotlin 1.1.4

        แต่ทว่ายังเป็น Experiment อยู่นะ (ถึงแม้ว่าตอนที่เขียนบทความนี้จะเป็น 1.1.51 แล้วก็ตาม) ซึ่งในการใช้งานจะต้องไปเปิดใช้งานใน build.gradle ก่อน โดยประกาศคำสั่งไว้ดังนี้

android {
    ...
    androidExtensions {
        experimental = true
    }
}

        เมื่อย้อนกลับมาที่คลาส Location ที่เจ้าของบล็อกต้องพิมพ์คำสั่งเพิ่มเข้าไปมากมายเพื่อทำให้เป็น Parcelable แต่ใช้ @Parcelize แทนก็จะเหลือคำสั่งแค่นี้แทน

@SuppressLint("ParcelCreator")
@Parcelize
class Account(var address: String, var zipCode: Int) : Parcelable

        ชีวิตดีโคตรๆอ่ะ

        แต่มีข้อจำกัดนิดหน่อยตรงที่ใช้ได้กับ Primary Constructor เท่านั้น (ในตอนนี้) และยังเป็น Experiment อยู่ แถมล่าสุดนั้นยังไม่รองรับกับคลาสที่ต้อง Extends จากคลาสที่เป็น Parcelable (Hierarchies of Parcelable classes) ถ้ารีบเอาไปใช้ก็ถือว่าแบกรับความเสี่ยงเองนะ เพราะล่าสุดเจ้าของบล็อกเจอปัญหาร้ายแรงอยู่นะ

แนะนำ 3rd Party Library/Plugin ตัวอื่นๆสำหรับทำ Parcelable ให้เป็นเรื่องง่าย

        จริงๆก็ไม่จำเป็นต้องเขียน Parcelable เองก็ได้ ในทุกวันนี้มีนักพัฒนาหลายๆคนทำ Library ขึ้นมาเพื่อลดภาระโค้ดในส่วนนี้ให้น้อยลง จากที่ลองไปไล่หามาก็จะมีประมาณนี้

        • Parceler
        • AutoParcel
        • AutoValue Parcelable Extension
        • PaperParcel
        • Smuggler

        ก็เลือกใช้ตามความถูกใจละกันเนอะ แต่ถ้าเลือกไม่ถูก ลองอ่านบทความ A comparison of Parcelable boilerplate libraries ของ Brad Campbell เพื่อช่วยในการตัดสินใจได้นะ เพราะเค้าเปรียบเทียบ Library แต่ละตัวให้แล้ว

        แต่ถ้าเป็น Plugin ขอแนะนำตัวนี้เลย Parcelable Code Generator(for kotlin) [JetBrains] จะได้ไม่ต้องมานั่งพิมพ์โค้ดเองให้เสียเวลา

สรุป

        ถึงแม้ว่า Kotlin จะเข้ามาเป็นที่นิยมมากขึ้นเรื่อยๆ แต่การที่จะตัดสินใจและเลือกใช้ Kotlin ในการพัฒนาแอปฯแอนดรอยด์ก็ควรศึกษาและทดลองอะไรหลายๆอย่างก่อนเพื่อความแน่ใจ เพราะยังมีอะไรอีกหลายๆอย่างที่ต้องศึกษาเพิ่มอีก ซึ่ง Parcelable ก็คือหนึ่งในนั้นเช่นกัน เพื่อที่จะได้ใช้ภาษาที่ดีด้วยวิธีที่ดีสร้างออกมาเป็นแอปฯที่ดีครับ