今天會開始規劃每個頁面狀態的資料結構,並先理解 data class 使用情境與定義,以下如有解釋不清或是描述錯誤的地方還請大家多多指教:
主要的目的為裝載資料並傳遞,我們可以使用 data class 來建立 DTO (Data Transfer Object) 或是 VO (Value Object),一個是資料傳輸用的物件、一個是用來呈現在畫面的資料物件:
// 接收資料來的結構 DTO
data class Product(
val name: String,
val description: Strinng,
val price: Int,
val amount: Int
)
// 介面只需顯示部份資料 VO
data class ProductPreview(
val name: String,
val description: Strinng
)
為了確保程式生成的一致性和有意義的行為,data class 必須符合以下條件:
val
或 var
abstract
, open
, sealed
或 inner
// 如果要不帶參數的方式生成物件,那就需要給予預設的參數值
data class User(val name: String = "", val age: Int = 0)
...
val user = User()
主構造函數的參數在 compile 後會產生以下的 method:
"User(name="", age=0)"
文字如果參數宣告在 class 內部的就不會實作上述的方法,來看看範例吧!一個參數是在主構造函數做宣告,一個是在 body 內做宣告:
data class Person(val name: String) {
var age: Int = 0
}
val person1 = Person("John")
val person2 = Person("John")
person1.age = 10
person2.age = 20
// person1 == person2: true
// person1 with age 10: Person(name=John)
// person2 with age 20: Person(name=John)
只有 name
會在 compile 實作那些方法,所以上面在做物件比對的時候只會比對 name,componentN()
也只會產出 component1()
。
copy 可以讓你改變物件的某些參數值,不會更動到原本的狀態:
val jack = Person(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
// Person(name=John, age=1)
// Person(name=John, age=2)
但是如果是集合類的資料就不會複製新的記憶體來使用,所以針對複製後的集合操作,都是對同一個記憶體位址的資料做更動,例如:
val jack = Person(name = "Jack", age = 1, hobby = mutableListOf("draw", "sing"))
val olderJack = jack.copy(age = 2)
olderJack.hobby.add("swim")
// Person(name=John, age=1, hobby=[draw, sing, swim])
// Person(name=John, age=2, hobby=[draw, sing, swim])
接下來要用 data class 來建立跟 API 接資料的 DTO 跟在頁面顯示的資料結構,我選這個 API 原因是因為它可以調整 response 來的參數,可以選擇我需要的資料,而每個參數所代表的意思可以閱讀他提供的文件,缺點可能就是他搜尋 API 只能輸入英文:
網站提供一個測試 API 回應的功能,可以先用這個功能來模擬 API 接上之後會給的 Response 以及要給的 Request 資料格式( weatherapi.com / 註冊取得個人的 API key ),從 app 畫面呈現來看我會需要預報跟搜尋,所以試打了 forecast 跟 search 這兩隻得到下面的結果:
{
"location": {
"name": "London",
"region": "City of London, Greater London",
"country": "United Kingdom",
"lat": 51.52,
"lon": -0.11,
"tz_id": "Europe/London",
"localtime_epoch": 1660963985,
"localtime": "2022-08-20 3:53"
},
"current": {
"last_updated_epoch": 1660963500,
"temp_c": 15.0,
"is_day": 0,
"condition": {
"text": "Clear",
"icon": "//cdn.weatherapi.com/weather/64x64/night/113.png"
},
"wind_kph": 11.2,
"humidity": 77,
"feelslike_c": 14.6,
"uv": 1.0
},
"forecast": {
"forecastday": [
{
"date": "2022-08-20",
"date_epoch": 1660953600,
"day": {
"maxtemp_c": 23.5,
"mintemp_c": 14.9,
"avgtemp_c": 19.1,
"maxwind_kph": 17.3,
"avgvis_km": 10.0,
"avghumidity": 59.0,
"daily_chance_of_rain": 0,
"condition": {
"text": "Overcast",
"icon": "//cdn.weatherapi.com/weather/64x64/day/122.png"
},
"uv": 4.0
},
"astro": {},
"hour": [
{
"time_epoch": 1660950000,
"time": "2022-08-20 00:00",
"temp_c": 17.1,
"is_day": 0,
"condition": {
"text": "Clear",
"icon": "//cdn.weatherapi.com/weather/64x64/night/113.png"
},
"wind_kph": 12.2,
"humidity": 60,
"feelslike_c": 17.1,
"chance_of_rain": 0,
"uv": 1.0
},
...
]
}
]
}
}
[
{
"id": 2478871,
"name": "Taipei",
"region": "T'ai-pei",
"country": "Taiwan",
"lat": 25.04,
"lon": 121.53,
"url": "taipei-tai-pei-taiwan"
},
...
]
以 search API 為範例:
data class SearchResponse(
val result: List<Search>
)
data class Search(
val id: Long,
val name: String,
val region: String,
val country: String,
val lat: Float,
val lon: Float,
val url: String
)
根據我們的 UI 決定要呈現什麼資料在上面,以主頁為範例:
sealed class HomeVo(val id: Int)
// display
data class CityCard(
val cityId: Int,
val cityName: String,
val day: String,
val temp: String,
val wind: String,
val wet: String,
val rain: String,
val isMarked: Boolean
) : HomeVo(cityId)
data class CityDayTemp(
val day: String,
val maxTemp: String,
val minTemp: String
)
data class CityHourTemp(
val cityId: Int,
val time: String,
val temp: String
) : HomeVo(cityId)
// empty
data class EmptyState(val cityId: Int) : HomeVo(cityId)