Skip to content

Flutter 離線寫入佇列:草稿、重試與衝突怎麼處理

離線優先不等於「失敗後一直重試」。使用者在捷運上編輯一張工單,應用重啟後仍應看見自己的修改;恢復網路時,同一操作不能提交兩次;如果另一台裝置已修改工單,還要給使用者一個可理解的衝突狀態。

把本機記錄當作主視圖 ​

UI 先從本機資料庫讀取,寫入時在一個交易中同時更新可見記錄和待同步佇列。佇列項至少保存 operationId、實體 ID、基礎版本、變更內容、建立時間及狀態。operationId 也發給伺服器端作為冪等鍵;逾時後的重試才不會生成重複工單。

text
使用者編輯 → 本機資料庫交易寫入草稿 + pending 操作 → UI 顯示「待同步」
網路可用 → 按實體依序提交 → 伺服器端確認版本 → 本機標記 synced
版本衝突 → 標記 conflict → 提供查看差異 / 重新提交入口

佇列要允許單項失敗,不能因為第一條格式錯誤就堵住所有工單。網路錯誤退避重試;權限失效暫停並等待重新登入;伺服器端明確拒絕時保留本機草稿並展示可操作原因。

衝突策略按欄位決定 ​

備註可以考慮追加式合併,金額和審核結果通常需要伺服器端裁決,不應使用「最後寫入覆蓋」。同步完成後用伺服器端回傳的最終實體替換本機快照,並清理已經確認的操作。背景任務在行動系統中不保證持續運行,因此前景恢復和使用者主動同步都應能推進佇列。

測試時覆蓋強制關閉應用後恢復、重複回執、兩台裝置同時編輯和權限過期。只測斷網期間能顯示草稿,無法證明同步安全。

一條工單從編輯到確認 ​

先定義本地事務:用戶點擊保存時,把工單草稿和 pending_operation 一起寫入數據庫。操作記錄至少包含穩定的 operationId、目標 ID、baseVersion、序列化後的變更、創建時間與狀態。草稿立即可見,但標記為“待同步”;不要先發 HTTP 再保存草稿,否則進程被系統回收時會出現服務端已處理、本地卻毫無記錄的斷層。

例如列車駛入隧道時發出請求,服務端實際創建成功,客戶端卻沒收到響應。重試必須使用同一個冪等鍵。服務端也必須在冪等窗口內保存鍵與結果,客戶端單方面生成 UUID 並不能防重。收到確認後,在本地事務中更新服務端版本、正式實體快照和操作狀態。若崩潰發生在響應到達後、確認寫盤前,下次用原鍵重試並取得同一結果。

text
local draft + operation(id=K, base=7)
  → POST /work-orders  Idempotency-Key: K
  → timeout: keep pending, retry K
  → 200(version=8): atomically replace snapshot and acknowledge K
  → 409(current=9): stop this entity's queue and surface a conflict

失敗要分流,不能只做指數退避 ​

網絡超時和 5xx 可以退避重試,401 應暫停隊列並等待重新認證,403 或校驗錯誤應轉為可處理的失敗,409 應保留本地改動並展示字段差異。不同實體可以獨立推進;同一實體的操作則必須按因果順序執行,避免“修改標題”先於“創建工單”到達。若用戶撤銷一條尚未發送的操作,先確認是否存在後續依賴,再決定合併或取消。

驗收時在發送前、服務器處理後但回包前、回包後但落盤前分別殺進程;再用兩台設備製造版本衝突。每次都檢查列表、待同步角標、服務端實體數和操作表,不能只看界面能否顯示草稿。

參考:Flutter 離線優先架構。

MIT Licensed