經友人 L 建議,可以透過 LifecycleScope 管理 coroutine 的生命週期。因為在此 LifecycleScope 範圍內的 coroutine 都會在 Lifecycle 被銷毀時自動取消。
在 Gradle(Module)內加入 :
androidx.lifecycle:lifecycle-runtime-ktx:2.4.0
將  CoroutineScope(Dispatchers.Main).launch {} 修改為用 LifecycleScope.launch {} 啟動 coroutine :
// 原始程式碼
CoroutineScope(Dispatchers.Main).launch {
}
// 修改後
lifecycleScope.launch(Dispatchers.Main) {
    try {
        binding.progressbar.visibility = View.VISIBLE
        fetchCoffeeShopData()
    }
    catch (e: CoffeeShopsRefreshError) {
        binding.progressbar.visibility = View.INVISIBLE
        binding.textView.text = "Request failed \nmessage: ${e.message}"
    }
}
修改好啦~執行起來也一切正常!

點擊按鈕後執行  refreshText() 來測試使用 async  啟動執行緒 :
// TODO: test async
binding.button.setOnClickListener {
    refreshText()
}
運作的流程大概是這樣 :
coroutine
async(Dispatchers.IO) 在 IO 執行緒啟動 coroutine執行非同步任務,這邊透過 delay(1000) 模擬非同步情形並回傳 Deferred<String> ,使用變數 rawText 儲存回傳結果await() 等待非同步的執行結果,取到後使用變數 deferredResult 儲存非同步結果private fun refreshText() {
    lifecycleScope.launch(Dispatchers.Main) {
        binding.progressbar.visibility = View.VISIBLE
        // 非同步執行取得 "async result"
        val rawText: Deferred<String> = lifecycleScope.async(Dispatchers.IO) {
            delay(1000)
            "async result"
        }
        // 等待非同步執行結果
        val deferredResult = rawText.await()
        // 更新
        binding.textView.text = deferredResult
        binding.progressbar.visibility = View.GONE
    }
}

我們成功拉~~~
看來方向沒有錯,接著在試著將取得非同步文字那段程式碼寫成方法提出去。
先看一眼 Kotlin 的方法要怎麼寫 :
要回傳的型別寫在冒號後面,看習慣 Java 的我只覺得眼睛業障重…
來改寫吧,因為未來是用呼叫 API 的方式來取得非同步結果,先從簡單的開始練習 :
// 待修改的程式碼
val rawText: Deferred<String> = lifecycleScope.async(Dispatchers.IO) {
            delay(1000)
            "async result"
}
加入 getAsyncText() :
/**
 * 非同步執行取得 "async result"
 */
private suspend fun getAsyncText(): Deferred<String> {
    return lifecycleScope.async(Dispatchers.IO) {
        delay(1000)
        "async result"
    }
}
refreshText() :
// 修改後
private fun refreshText() {
    lifecycleScope.launch(Dispatchers.Main) {
        binding.progressbar.visibility = View.VISIBLE
        // 等待非同步執行結果
        val deferredResult = getAsyncText().await()
        // 更新
        binding.textView.text = deferredResult
        binding.progressbar.visibility = View.GONE
    }
}
再次成功了~~~

每天都寫個一小點,慢慢地就會完成專案了吧~~~
目前進度有點落後呀