**Build #186** — Release APK (signed)
Commit: 6afd830587723c211c2e4ca6f9330d2b85fdfe17
fix(orders): แท็บออเดอร์ไม่เด้ง "เกิดข้อผิดพลาด" ตอนเปิดจอแล้ว
อาการ: เปิดแท็บออเดอร์ → ขึ้นจอ error "เกิดข้อผิดพลาด /
StandaloneCoroutine was cancelled" ทั้งที่ข้อมูลโหลดมาครบแล้ว
E OrdersViewModel: Error loading orders
E OrdersViewModel: kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled
## สาเหตุ
loadOrders() ขึ้นต้นด้วย ordersJob?.cancel() แล้ว launch รอบใหม่ทับ
แต่ cancel() แค่ "ขอให้หยุด" — job เก่าที่ค้างอยู่ใน Room query ยังวิ่งต่อจนคิวรีจบ
แล้วค่อยเด้ง CancellationException ตอน resume ซึ่งอาจเกิด *หลัง* job ใหม่
เซ็ต state สำเร็จไปแล้ว
→ catch (e: Exception) กินเคสนี้ด้วย แล้วเขียน error = e.message ทับของใหม่
state ที่โหลดสำเร็จหายไป กลายเป็นจอ error ทั้งที่ไม่มีอะไรพัง
จังหวะที่ชนตอนเข้าจอ: init เรียก loadOrders() แล้ว refresh() → fetchOrders() →
loadOrders(showLoading = false) มายกเลิก job แรกทันที
## ทำอะไร
- ทุก catch ในไฟล์ (14 จุด) rethrow cancellation ก่อนเสมอ:
catch (e: CancellationException) { throw e } นำหน้า catch (e: Exception)
ใช้ kotlinx.coroutines.CancellationException ให้ตรงกับ SmsHistoryViewModel /
SmsMatcherViewModel / ParallelSyncHelper ที่ทำแบบนี้อยู่แล้ว (typealias ตัวเดียวกัน)
OrdersViewModel เป็นไฟล์เดียวที่เหลือในโปรเจกต์ที่ยังไม่ได้ทำ
- loadOrders() เพิ่ม ensureActive() ก่อนเขียน state ตอน "สำเร็จ" ด้วย —
rethrow กัน job ที่ถูกยกเลิกเขียน *error* ทับ แต่ job ที่ถูก cancel หลังพ้น
suspend point สุดท้ายยังเขียน *ข้อมูลเก่า* ทับของที่สดกว่าได้
- loadSlipImage() rethrow ด้วย — เดิมปิดจอ/เลื่อนพ้นการ์ดกลางโหลดรูป
จะถูกตีเป็น "โหลดรูปสลิปไม่ได้"
## เลิกโชว์ข้อความ exception ดิบ (trap: Raw exception display)
- OrdersState.error: String? → hasLoadError: Boolean
ข้อความ exception ไม่เข้า state อีกแล้ว ไปอยู่ใน Logcat อย่างเดียว
- จอ error ใช้ AppStrings: ordersErrorTitle / ordersErrorBody / ordersErrorRetry
ประกาศเป็น class-body property ไม่ใช่ constructor param — ไฟล์นั้นเตือนเรื่อง
เพดาน 255 args ของ dex ที่ทำให้ ART VerifyError ตอนเปิดแอพ
- approve/forceApprove/reject/void เลิกยัด e.message ลง snackbar → ข้อความไทยคงที่
- ตัด prefix "Error: " ในจอออเดอร์ ซ้ำกับ "ไม่สำเร็จ: " ที่ toResult() ใส่มาแล้ว
## ทดสอบบน AVD crashtest (Android 14)
กับดัก: DB ว่างทำให้คิวรีเร็วเกินจนไม่มีช่วงชน — เทสจะ "ผ่าน" ทั้งที่โค้ดยังพัง
(ยืนยันแล้วว่าโค้ดก่อนแก้ก็ผ่าน) เลยแทรก race probe ชั่วคราวให้ job แรกเท่านั้น
ค้างแบบ cancel ไม่ได้ withContext(Dispatchers.IO) { Thread.sleep(2500) }
แล้ว A/B ด้วย window เท่ากัน
| build | จอออเดอร์ | logcat |
|---|---|---|
| ก่อนแก้ + probe | "เกิดข้อผิดพลาด / StandaloneCoroutine was cancelled" | E OrdersViewModel x2 |
| หลังแก้ + probe | จอว่างปกติ "ยังไม่มีออเดอร์" | เงียบ |
| หลังแก้ ถอด probe | ปกติ ทั้งเข้าจอ / ค้นหา / สลับแท็บรัวๆ / กดย้อนกลับ | เงียบ |
ถอด probe แล้ว diff เทียบกับ patch ที่รีวิว = ตรงกัน ไม่มีโค้ดเทสหลงเหลือ
assembleDebug + testDebugUnitTest ผ่านทั้งคู่
Co-Authored-By: Claude Opus 5 <
[email protected]>