iOS| #25 | 約翰紅茶訂餐App 2.0–Part.1 串接Airtable API
不久前已經實作過訂飲料App初版,那麼這次要利用目前所學的技能將其升級為2.0,並且加上數個功能使其成為一個更完整的產品。
本篇文章主要會說明Airtable設計Model的架構與App設計Model的架構。
由於此系列文篇幅會較長,會拆分成多篇文章紀錄實作的過程與功能講解
技術應用
- Airtable REST API — CRUD
- Result Type
- 關聯式資料庫
Model設計 — Airtable
- DrinkType
維護品項的類型,後續會利用此設定檔table產生出水平換頁選擇飲料Menu的功能。(參考:iOS| #24 | 搭配動畫的水平滑動底線按鈕與頁面控制)
- Menu
維護所有品項的資料,後續將使用此設定檔table展開所有飲料品項提供User做選擇
- OrderHeader
透過App維護訂單的相關的表頭資訊將會記錄在此table
這邊會利用到關連式資料庫的基本觀念,將表頭存在一個table中,藉由欄位OrderNumber關聯至表身Table — OrderItem,這樣假如有多筆item的話就只需要記一筆表頭的資料就好,不需要在每一筆item都記住一樣的表頭資料。
備註:後續實作發現Airtable的機制並不是這麼簡單,這裡的欄位OrderNumber並不是肉眼看到的”20210215000001”,call api時會返回關聯的table欄位id的string
- OrderItem
透過App維護訂單的相關的表身資訊將會記錄在此table
OrderItem — OrderNumber關聯到OrderHeader — OrderNumber
OrderItem — itemID關聯到Menu — itemID
ER Model:
Model設計 — Swift
- NetWorkController
有關網路的功能會使用它來實作
- Ice
飲料冰塊
- Sugar
飲料甜度
- Size
飲料尺寸
- Status
訂單狀態
因為以下資訊橫跨多個頁面都會用到,再加上正常情況使用下,並不會有龐大的資料量。
在這邊設計讓以下兩個項目作為global data活在程式裡:
- Cart
購物車項目
- HeaderInfo
表頭資訊
功能解說&程式解說
- Result Type
在說明Call API的程式以前要先了解Result Type的用法,因為實作CRUD的程式都是使用Result Type來得出結果的。
首先我們來看一小段程式:
FoodType為食物類別:在這邊有兩種食物,分別為MCChicken (麥脆雞)、KFCChicken (肯德基)
enum FoodType {case MCChicken //麥脆雞case KFCChicken //肯德基}
ErrorChicken為錯誤訊息:在這邊只需要判斷是不是肯得基就好
屬性errorMessage是型態為String的錯誤訊息
//errorenum ErrorChicken: Error { case isNotKFCChicken var errorMessage: String { switch self { case .isNotKFCChicken: return "這不是肯德基" }
}
}
實作function判斷是否為肯德基,並且使用Result Type。
若為肯德基,則使用.success回傳string;不是肯德基就使用.failure回傳error
//判斷是不是肯德基func checkChicken(food: FoodType , handler: @escaping (Result<String,ErrorChicken>) -> Void){ switch food{ case .KFCChicken: handler(.success("肯德基好吃")) case .MCChicken: handler(.failure(ErrorChicken.isNotKFCChicken)) }}
各自宣告型態為麥脆雞、肯德基的常數
//生出麥脆雞let MCchicken = FoodType.MCChicken//生成肯德基let KFCChicken = FoodType.KFCChicken
將前面宣告好的常數當作參數傳入function checkChicken中。
並且根據result判斷.success/.failure 分別要做什麼事
checkChicken(food: MCchicken) { result in switch result{ case .success(let string): print("沒錯!\(string)") case .failure(let error): print("嗚嗚嗚~\(error.errorMessage)") }} // 結果:嗚嗚嗚~這不是肯德基checkChicken(food: KFCChicken) { result in switch result{ case .success(let string): print("沒錯!\(string)") case .failure(let error): print("嗚嗚嗚~\(error.errorMessage)") }} // 結果:沒錯!肯德基好吃
可以看得出來藉由Result Type可以讓source code更有可讀性與維護性。
那麼接下來就說明使用 CRUD API的部分,以下範例都由Airtable - OrderHeader來做說明。
- 讀取資料(GET)
先複製一份URL string
var urlStringWithCondition = urlString
幫url String加上where條件。(某些特別符號或中文URL可能會認不得,所以記得要使用addingPercentEncoding轉換成URL認得的格式。)
注意:在這邊使用了Airtable的function “SEARCH”,可以搜尋特定欄位的特定資料。詳情請參考官方文件
if let field = condition.field,let value = condition.value{ urlStringWithCondition = urlStringWithCondition + "&filterByFormula=" + "SEARCH(\"\(value)\",{\ (field)})".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!}
將string轉換成URL,若失敗的話則丟出Error
guard let url = URL(string: urlStringWithCondition) else {completion(.failure(.invalidurl))return }
產生data task上網抓資料
URLSession.shared.dataTask(with: url) { data, response, error in 抓資料程式}.resume()
檢查是否有錯誤,若有錯誤的話則丟出Error
if let error = error {completion(.failure(.requestFailed))return}
檢查status是否為OK,若失敗的話則丟出Error
guard let httpResponse = response as? HTTPURLResponse,httpResponse.statusCode == 200else {completion(.failure(.invalidResponse))return}
檢查是否有抓到資料,若失敗的話則丟出Error
guard let data = dataelse {completion(.failure(.invalidData))return}
解析JSON格式的資料,若失敗的話則丟出Error
let decoder = JSONDecoder()guard let orderHeader = try? decoder.decode(OrderHeader.self, from: data)else {completion(.failure(.invalidJsonFormat))return }
前面都成功的話就會走到這一步,最後將解析完的資料透過.success傳出去
completion(.success(orderHeader))
- 新增資料(POST)
可以看得出來與讀取資料的程式十分雷同,接下來將會說明差異的地方,其餘相同部分不再贅述。
建立request
var request = URLRequest(url: url)
設好httpMethod
request.httpMethod = "POST"
給api所需的http Header值(建立好table後在官方文件裡看找到curl,就可以利用它來知道要填哪些資訊)
request.setValue("Bearer \(NetWorkController.apikey)", forHTTPHeaderField: "Authorization")request.setValue("application/json", forHTTPHeaderField: "Content-Type")
將組好的資料轉為JSON格式,並把值pass給http body
let encoder = JSONEncoder()let data = try? encoder.encode(orderHeader)request.httpBody = data
其餘部分可以參考上述讀取資料的說明。
- 修改資料(PATCH)
首先要了解PATCH、PUT都是跟Update Table有關的method,根據官方文件說明:
濃縮內容大致上為使用PATCH只會更改指定的fields;使用PUT會將指定的fields以外的欄位都清空。
只透過文字說明可能還是會有一些理解上的困難,我們來看看下述例子吧!
首先我們以訂單編號“20220226000002”為例子。
使用PATCH更新資料,將欄位totalPrice改為100。
使用PUT更新資料,將欄位totalPrice改為125。
以上例子來看感覺沒有什麼差異,這是因爲我們同時有指定其他的fields,以下我們試看看在body只指定totalPrice與id:
使用PATCH更新資料,將欄位totalPrice改為100。
使用PUT更新資料,將欄位totalPrice改為125。
可以明顯地看到使用PUT的話,totalPrice一樣成功被修改了,但由於其他欄位沒有被指定的關係,會全部被清空。
備註:createdTime是因為欄位屬性本身就會自動產生值,所以沒有被清空。
為了避免資料被清空,在這邊我們使用PATCH來修改訂單。
可以看得出來與新增資料的程式更是如出一徹,差別只在於httpMethod改為PATCH。
request.httpMethod = "PATCH"
其餘部分可以參考上述新增資料的說明。
- 刪除資料(DELETE)
可以看得出來與修改資料的程式非常相似,接下來將會說明差異的地方,其餘相同部分不再贅述
組query string(在這邊只考慮刪除單筆的情況),可以參考官方文件給的範例
var urlStringWithCondition = urlString + "?records[]=\(id)"
httpMethod改為DELETE
request.httpMethod = "DELETE"
系列文章:
若內容有誤煩請指教,感謝收看。