在介接 API 的時候碰到一個東西,要把自定義的 data class 實作 Parcelable ,而這個 data class 裡面又有包含另外一個 Parcelable 的 List。
而我的目標是
在不需要改寫原本的 primary constructor 以及把變數從 val 換成 var ,以及不需要多寫其他 class method 就可以達成
中間試了很多方法,看了很多文章有用了 library 的,和一些 Java 的實作方式。
但是看到 Java 的實作方式裡面的屬性都沒有被加上 final ,也就是可變化(mutable)、在 Kotlin 的話是 var
,因此都不是我想要的做法。
基本的東西
還沒加上 Parcelable 各自像是這樣:
data class Product(val id: String?,
val name: String?,
val images: List<Media>?)
data class Media(val small: String?,
val large: String?)
讓 Media 實作 Parcelable
處理這部分的實作的時候。我的順序是從內實作到外,也就是先從 Media 開始,因為沒有包含其他自定義型別,所以處理上比較簡單。
在 class name 定義後面加上 Parcelable 之後, Android Studio 會有紅色燈泡,點下去就可以自動生成所有所需要的 methods ,完成之後像是這樣:
// 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
原本長這個樣子
data class Product(val id: String?,
val name: String?,
val images: List<Media>?)
寫一寫就卡在這邊:
// 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 看看:
// 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 的目的了!