iT邦幫忙

2023 iThome 鐵人賽

DAY 15
0

昨天才剛把專案調整為 MVP 架構,本來今天想直接改為 MVVM,但突然有點不捨,所以改為解析 Json 字串好了,過幾天在來重構~~

定義資料結構

Json 是一種資料交換的格式,由鍵-值對 (key-value) 組成。也就是說需要將取得的 API 回傳資料透過鍵-值將資料解析出來,所以先來看一下這支 API 的欄位說明,這樣才會知道我們要的資料是以哪種 key 儲存 :

d15_1.png

試著用 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>>) {}

d15_2.png

接著調整 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")
            }
        }
    }
}

重構 MainPresenter

這邊也是調整 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)
            }
        })
    }
}

重構 MainView

最後來到 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)

中秋節快樂呀~大家~~

今日推推

Yes


上一篇
Day14 重構 | 使用 MVP 作為 Kotlin App 的設計架構
下一篇
Day16 使用 RecyclerView 顯示 API 資料
系列文
喝咖啡要30天?一起用 Kotlin 打造尋找好喝咖啡的 App30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Jim
iT邦新手 4 級 ‧ 2023-09-30 10:27:43

公司的舊專案幾乎都用 for (i in 0 until jsonArray.length()) {} 去逐一解析 json,看久了會有想重構的念頭 XD

helloiris iT邦新手 5 級 ‧ 2023-09-30 21:03:24 檢舉

XDDD前面加了 Gson,結果後面接資料還忘記用了XDD

我要留言

立即登入留言