LoginSignup
0
0

More than 5 years have passed since last update.

Kotlin, Parcelable, Custom Class 和 List

Last updated at Posted at 2017-09-20

在介接 API 的時候碰到一個東西,要把自定義的 data class 實作 Parcelable ,而這個 data class 裡面又有包含另外一個 Parcelable 的 List。

而我的目標是

在不需要改寫原本的 primary constructor 以及把變數從 val 換成 var ,以及不需要多寫其他 class method 就可以達成

中間試了很多方法,看了很多文章有用了 library 的,和一些 Java 的實作方式。
但是看到 Java 的實作方式裡面的屬性都沒有被加上 final ,也就是可變化(mutable)、在 Kotlin 的話是 var ,因此都不是我想要的做法。

基本的東西

還沒加上 Parcelable 各自像是這樣:

Product.kt
data class Product(val id: String?, 
                   val name: String?,
                   val images: List<Media>?)
Media.kt
data class Media(val small: String?,
                 val large: String?)

讓 Media 實作 Parcelable

處理這部分的實作的時候。我的順序是從內實作到外,也就是先從 Media 開始,因為沒有包含其他自定義型別,所以處理上比較簡單。

在 class name 定義後面加上 Parcelable 之後, Android Studio 會有紅色燈泡,點下去就可以自動生成所有所需要的 methods ,完成之後像是這樣:

Media.kt
// package 略
// import 略

data class Media(val small: String?,
                 val large: String?): Parcelable {

    constructor(parcel: Parcel) : this(
            parcel.readString(), // Parcelable 有辦法處理 nullable types, 因此這樣寫沒有問題
            parcel.readString())

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(small)
        parcel.writeString(large)
    }

    override fun describeContents(): Int = 0

    companion object CREATOR : Parcelable.Creator<Media> {
        override fun createFromParcel(parcel: Parcel): Media = Media(parcel)
        override fun newArray(size: Int): Array<Media?> = arrayOfNulls(size)
    }
}

*在這裡部分覆寫的函式只有一行,我就拿掉 Android Studio 產生給我的 { } ,直接用 = 的方式寫

讓 Product 實作 Parcelable

原本長這個樣子

Product.kt
data class Product(val id: String?, 
                   val name: String?,
                   val images: List<Media>?)

寫一寫就卡在這邊:

Product.kt
// package 略
// import 略

data class Product(val id: String?, 
                   val name: String?,
                   val images: List<Media>?): Parcelable {

    constructor(parcel: Parcel) : this(
            parcel.readString(),
            parcel.readString(),
            /** 這邊要怎麼把它讀出來?! **/) // <<<< ---- 就是這裡

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(id)
        parcel.writeString(name)
        parcel.writeTypedList(images) // 怎麼塞進去是沒問題
    }

    override fun describeContents(): Int = 0

    companion object CREATOR : Parcelable.Creator<Product> {
        override fun createFromParcel(parcel: Parcel): Product = Product(parcel)
        override fun newArray(size: Int): Array<Product?> = arrayOfNulls(size)
    }

}

一開始查到怎麼處理這個 List 的時候,看到這樣的東西:

val medium = mutableListOf<Media>() 
parcel.readTypedList(medium, Media.CREATOR)

因為 readTypedList 這個方法是沒有回傳值的,所以如果這樣寫的話,就一定要找個辦法把生成完的 medium 塞回原本的 images 屬性裡面

但是因為 images 他是一個 immutable 變數(val),所以一者不是要讓他變成 var ,在 Parcel 用的 constructor 才有辦法修改這個值;不然就是要另外寫一個 class method (也就是 companion object),先處理完這個 List 再去執行初始化。

但是這樣子就只是因為實作 Parcel ,原本簡潔漂亮的 Kotlin data class ,就會變得醜醜的,其實不是那麼地願意。

apply

當絕望之間,無意間就發現 apply 的這個東西
他是屬於 Kotlin standard library 的高階語法

宣告是這樣:

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

在呼叫這個方法之後,會有個回傳值,並且是回傳自己。

他有這樣的特性

  • 在 apply 的 block 中,可以用 this 代表呼叫 apply 的物件
  • 當要使用該物件的方法時,可以省略 this
  • 一定會有返回值,返回值則是是呼叫 apply 的物件。這個回傳則不用自己寫 return this

於是就開始用用看,
單獨的寫的話是這樣:

val medium = mutableListOf<Media>().apply {
    parcel.readTypedList(this, Media.CREATOR)
}

喔喔!
看起來好像可行!

把它加回去 Product 的 Parcel 用 constructor 看看:

Product.kt
// package 略
// import 略

data class Product(val id: String?, 
                   val name: String?,
                   val images: List<Media>?): Parcelable {

    constructor(parcel: Parcel) : this(
            parcel.readString(),
            parcel.readString(),
            mutableListOf<Media>().apply { parcel.readTypedList(this, Media.CREATOR) }) 
            // ↑↑↑ 這一段

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(id)
        parcel.writeString(name)
        parcel.writeTypedList(images)
    }

    override fun describeContents(): Int = 0

    companion object CREATOR : Parcelable.Creator<Product> {
        override fun createFromParcel(parcel: Parcel): Product = Product(parcel)
        override fun newArray(size: Int): Array<Product?> = arrayOfNulls(size)
    }

}

建置之後,該處理的資料也有如預期順利運作
終於順利在沒有修改原有設計的方式,達成漂亮的掛上 Parcelable 的目的了!

參考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0