接下來幾天會開始將筆記加入實作的 App ,前面第一階段的什麼?還是以筆記的型態講解每篇的主題,第二階段的如何?會開始以 app 的結構開始實作,以下如有解釋不清或是描述錯誤的地方還請大家多多指教:
sealed class 跟 interfaces 都是被設定為受限的繼承結構。sealed class 本身是抽象類別,本身不能直接實例化,而 constructor 必須為 protected 或是 private ,subclass 需要有命名,不可為 local 或是匿名物件,檔案位置也須和 sealed class 在同個 package 中 。
sealed class Character {
// protected by default
constructor() { /*...*/ }
// private is OK
private constructor(description: String): this() { /*...*/ }
// Error: public and internal are not allowed
public constructor(code: Int): this() {}
}
kotlin 1.0
時 subclass 必須在 sealed class 內部kotlin 1.1
subclass 不必在 sealed class 內部,但仍需要在同個文件中kotlin 1.5
可定義在不同檔案中,但需要在同個 package 中
因為這樣的結構讓我們可以透過 sealed class 來呈現資料處理狀態,像是一個購物清單的頁面物件需要有讀取中、顯示、空的、錯誤等等狀態,可以針對購物清單設定一個 sealed class 和各個需要的狀態的 subclass,這些資料狀態就只限於購物清單。或者是今天要切分各個錯誤類別,因為讀取資料造成 IOError 的,可能分成檔案讀取、資料庫讀取等等。
sealed class ShopListState(val id: Int)
data class ShopLoadingState(val shopId: Int): ShopListState(shopId)
data class ShopData(val shopId: Int, val source: shopData): ShopListState(shopId)
data class ShopError(val shopId: Int, val error: String): ShopListState(shopId)
某種程度上和之後會提到的 enum class 很像,只是差異在 sealed class 的 subclass 可以持有多個擁有自己狀態的 instances ,而 enum 的存在被視為單ㄧ instance。
enum
classes can't extend a sealed class (as well as any other class), but they can implement sealed interfaces.
上面有提到 sealed class 跟 sealed interfaces,那這兩者之間又有什麼不一樣呢?sealed interfaces 是在 Kotlin 1.5.0 才推出的,兩者都有 compile 過後就不能被第三方實作的特性,以下整理出幾個特點:
interface Human {
fun walk()
fun eat()
}
// 當想要開出不想被第三方實作的接口時,就可以用 sealed class
sealed interface Human {
fun walk()
fun eat()
}
enum class Action {
WALK, RUN, JUMP
}
// 透過反譯可以得知 action 繼承了 Enum,因為單繼承的結構所以 enum 不行繼承其他 class,
// 但可以 implement 多個 interface
sealed interface Human
enum class Action : Human {
WALK, RUN, JUMP
}
enum class Age : Human {
KID, TEENAGER, ADULT, ELDERLY
}
// 因為 sealed class 為抽象類,每個物件都只能繼承一個抽象類別,當要延伸較複雜的結構關係時
sealed interface Error
sealed interface Runtime
sealed class IOError(): Error
class FileReadError(val file: File): IOError()
class DatabaseError(val source: DataSource): IOError()
object RuntimeError : Error, Runtime
app 有兩個部分會用到 sealed class ,就如上面提到的 sealed class 很適合用在元件的資料狀態,以及切分錯誤類別,我會在 app 有打 api 的頁面都建立一個 UI state sealed class 以及建立一個接 api response 的 sealed class 切分 loading, success, error 的狀態。如以下:
// 以下分別建立在 home, detail, search package 中
sealed class HomeVo
sealed class DetailVo
sealed class CitiesVo
// 之後接 API 時就可以知道資料回傳成功或失敗及 loading 的狀態
sealed class Resource<T>(
val data: T? = null,
val message: String? = null
) {
class Loading<T> : Resource<T>()
class Success<T>(data: T) : Resource<T>(data = data)
class Error<T>(errorMessage: String) : Resource<T>(message = errorMessage)
}
UI state 的 subclass 在後續的篇章再繼續補上,目前先開出需要用到的 sealed class,基本上成功會有 display state, empty state,失敗和 loading 都會以 resource 送出去的狀態決定。
Reference
How to Handle API Responses (Success/Error) in Android?
Sealed Interface