昨天才剛把專案調整為 MVP 架構,本來今天想直接改為 MVVM,但突然有點不捨,所以改為解析 Json 字串好了,過幾天在來重構~~
Json 是一種資料交換的格式,由鍵-值對 (key-value) 組成。也就是說需要將取得的 API 回傳資料透過鍵-值將資料解析出來,所以先來看一下這支 API 的欄位說明,這樣才會知道我們要的資料是以哪種 key 儲存 :
試著用 postman 呼叫,可以看到這就是目前的資料長相 :
[
{
"id": "00014645-38c8-4eb4-ad9b-faa871d7e511",
"name": "R5小餐館",
"city": "chiayi",
"wifi": 5,
"seat": 5,
"quiet": 5,
"tasty": 5,
"cheap": 5,
"music": 5,
"url": "https://www.facebook.com/r5.bistro",
"address": "嘉義市東區忠孝路205號",
"latitude": "23.48386540",
"longitude": "120.45358340",
"limited_time": "maybe",
"socket": "maybe",
"standing_desk": "no",
"mrt": "",
"open_time": "11:30~21:00"
}
]
這是一個 array object 結構,表示我們可能會收到多筆資料,直接來定義我們的 data 吧!
如果想要儲存資料,Kotlin 有一種特殊的類別 data class,大多是用於儲存資料,先看範例 :
data class Person(val name: String, val age: Int)
定義好的資料結構,Cafe :
data class Cafe(
val id: String,
val name: String,
val city: String,
val wifi: Int,
val seat: Int,
val quiet: Int,
val tasty: Int,
val cheap: Int,
val music: Int,
val url: String,
val address: String,
val latitude: String,
val longitude: String,
val limited_time: String,
val socket: String,
val standing_desk: String,
val mrt: String,
val open_time: String
)
這裡我們用 Gson library 來幫我們解析,省去一個一個把他取出來的步驟,所以先在 Gradle(Module) 加入:
// Gson
implementation 'com.google.code.gson:gson:2.9.0'
將 MainModel
callback 的回傳格式由 String? 改為 List :
override suspend fun getCoffeeShops(city: String?, callback: BaseContract.BaseCallback<List<Cafe>>) {}
接著調整 MainModel
解析資料的程式碼 :
import com.helloiris.taoyuan.findyourcoffee.model.Cafe
// 建立回傳的資料
val cafeList = mutableListOf<Cafe>()
// 解析資料
for (i in 0 until jsonArray.length()) {
val jsonObject = jsonArray.getJSONObject(i)
val id = jsonObject.getString("id")
val name = jsonObject.getString("name")
val city = jsonObject.getString("city")
val wifi = jsonObject.getInt("wifi")
val seat = jsonObject.getInt("seat")
val quiet = jsonObject.getInt("quiet")
val tasty = jsonObject.getInt("tasty")
val cheap = jsonObject.getInt("cheap")
val music = jsonObject.getInt("music")
val url = jsonObject.getString("url")
val address = jsonObject.getString("address")
val latitude = jsonObject.getString("latitude")
val longitude = jsonObject.getString("longitude")
val limitedTime = jsonObject.getString("limited_time")
val socket = jsonObject.getString("socket")
val standingDesk = jsonObject.getString("standing_desk")
val mrt = jsonObject.getString("mrt")
val openTime = jsonObject.getString("open_time")
val cafe = Cafe(
id, name, city, wifi, seat, quiet, tasty, cheap, music, url, address,
latitude, longitude, limitedTime, socket, standingDesk, mrt, openTime
)
cafeList.add(cafe)
}
// 回傳結果
callback.onSuccess(cafeList)
}
catch (e: JSONException) {
e.printStackTrace()
callback.onFail(e.message)
}
MainModel
:
class MainModel: MainContract.Model {
override suspend fun getCoffeeShops(city: String?, callback: BaseContract.BaseCallback<List<Cafe>>) {
withContext(Dispatchers.IO) {
// 創建一個 OkHttpClient 實例
val client = OkHttpClient()
// 設置要發送的 HTTP 請求
val request = Request.Builder()
.url("http://cafenomad.tw/api/v1.2/cafes/$city")
.build()
val response = try {
// 使用 OkHttpClient 發送同步請求
client.newCall(request).execute()
}
catch (cause: IOException) {
callback.onFail(cause.message)
return@withContext
}
if (response.isSuccessful) {
val json = response.body?.string()
try {
// 建立 JsonArray
val jsonArray = JSONArray(json)
// 建立回傳的資料
val cafeList = mutableListOf<Cafe>()
// 解析資料
for (i in 0 until jsonArray.length()) {
val jsonObject = jsonArray.getJSONObject(i)
val id = jsonObject.getString("id")
val name = jsonObject.getString("name")
val city = jsonObject.getString("city")
val wifi = jsonObject.getInt("wifi")
val seat = jsonObject.getInt("seat")
val quiet = jsonObject.getInt("quiet")
val tasty = jsonObject.getInt("tasty")
val cheap = jsonObject.getInt("cheap")
val music = jsonObject.getInt("music")
val url = jsonObject.getString("url")
val address = jsonObject.getString("address")
val latitude = jsonObject.getString("latitude")
val longitude = jsonObject.getString("longitude")
val limitedTime = jsonObject.getString("limited_time")
val socket = jsonObject.getString("socket")
val standingDesk = jsonObject.getString("standing_desk")
val mrt = jsonObject.getString("mrt")
val openTime = jsonObject.getString("open_time")
val cafe = Cafe(
id, name, city, wifi, seat, quiet, tasty, cheap, music, url, address,
latitude, longitude, limitedTime, socket, standingDesk, mrt, openTime
)
cafeList.add(cafe)
}
// 回傳結果
callback.onSuccess(cafeList)
}
catch (e: JSONException) {
e.printStackTrace()
callback.onFail(e.message)
}
}
else {
callback.onFail("Unable to refresh data")
}
}
}
}
這邊也是調整 callback 的回傳值 :
class MainPresenter(iModel: MainContract.Model, iView: MainContract.View) : MainContract.Presenter {
private val iModel: MainContract.Model
private val iView: MainContract.View
init {
this.iModel = iModel
this.iView = iView
}
override suspend fun getCoffeeShops(city: String?) {
iModel.getCoffeeShops(city, object : BaseContract.BaseCallback<List<Cafe>?> {
override fun onFail(errMsg: String?) {
iView.showFail(errMsg)
}
override fun onSuccess(cafes: List<Cafe>?) {
iView.showCoffeeShop(cafes)
}
})
}
}
最後來到 MainActivity
,我們本來在成功後是顯示全部的資料,先改成顯示第一筆資料就好 :
override fun showCoffeeShop(cafes: List<Cafe>) {
runOnUiThread {
if (cafes.isNotEmpty()) {
val cafe = cafes[0]
binding.textView.text = cafe.toString()
}
binding.progressbar.visibility = View.GONE
}
}
調整完後就來運行一下,這樣有沒有清楚多了~~
明天我們在用 RecyclerView
重新呈現資料,就會更整齊一點!
https://github.com/helloiris0216/IThome15th_pic/blob/master/day15/d15_3.png?raw=truehttps://prod-files-secure.s3.us-west-2.amazonaws.com/caa54e63-1695-4891-b6b8-caed035265d4/9b37d9ae-e999-44bd-a6c9-ccb7e2efa2d5/d15_3.png)
中秋節快樂呀~大家~~
公司的舊專案幾乎都用 for (i in 0 until jsonArray.length()) {} 去逐一解析 json,看久了會有想重構的念頭 XD
XDDD前面加了 Gson,結果後面接資料還忘記用了XDD