<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Data-Refresh on Tarragon</title><link>https://tarrragon.github.io/blog/tags/data-refresh/</link><description>Recent content in Data-Refresh on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Thu, 16 Jul 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/data-refresh/index.xml" rel="self" type="application/rss+xml"/><item><title>U.C6 加書後返回不刷新統計 — 只設計了進入時載入</title><link>https://tarrragon.github.io/blog/ux-design/cases/back-navigation-stale-statistics/</link><pubDate>Thu, 16 Jul 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/ux-design/cases/back-navigation-stale-statistics/</guid><description>&lt;p>book_overview_app 是一個 Flutter 書庫管理教學專案，以下案例取自該專案的實機測試。happy-path-only 反模式不只出現在導航流、也出現在資料流——畫面設計只回答「進入時顯示什麼」，沒回答「底層資料在畫面存活期間變更時，畫面如何得知」。&lt;/p>
&lt;h2 id="觀察">觀察&lt;/h2>
&lt;p>book_overview_app 的資料管理頁顯示書庫統計（書籍總數、書庫狀態）。統計只在 &lt;code>initState&lt;/code> 的 &lt;code>addPostFrameCallback&lt;/code> 呼叫一次 &lt;code>loadData()&lt;/code>。&lt;/p>
&lt;p>實機測試路徑：資料管理頁（顯示無書目）→ 點「搜尋加書」進入新增流程 → 成功加入一本書 → pop 返回資料管理頁。返回後統計仍顯示無書目 — Flutter 的 push/pop 導航不會 dispose/rebuild 底下頁面的 State，&lt;code>initState&lt;/code> 不會再跑一次。使用者要退回首頁再重新進入資料管理頁，統計才更新。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>項目&lt;/th>
 &lt;th>修復前&lt;/th>
 &lt;th>修復後（W1-085）&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>統計載入時機&lt;/td>
 &lt;td>僅 &lt;code>initState&lt;/code> 一次&lt;/td>
 &lt;td>導航返回後再載入一次&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>加書後返回&lt;/td>
 &lt;td>停留舊值，需回首頁再進入&lt;/td>
 &lt;td>立即反映最新統計&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>實作&lt;/td>
 &lt;td>三個導航入口各自 &lt;code>pushNamed&lt;/code>&lt;/td>
 &lt;td>三個入口收斂為 &lt;code>_navigateAndRefresh&lt;/code> helper：&lt;code>await pushNamed(...)&lt;/code> 完成後呼叫 &lt;code>viewModel.loadData()&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>修復後規格同步新增 BR-10「衍生統計 reactive 一致性」：衍生統計必須響應底層書庫資料變更自動更新（Riverpod &lt;code>ref.watch&lt;/code> reactive 監聽），不得依賴頁面重進或手動刷新 — 這是比 await-then-refresh 更徹底的收斂方向。&lt;/p>
&lt;h2 id="判讀">判讀&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>happy-path-only 反模式的資料版本&lt;/strong>。導航版的 happy path 是「使用者只會往前走」，資料版的 happy path 是「畫面顯示的資料在畫面存活期間不會變」。本案的畫面狀態矩陣若有填，「進入條件」欄只會有一條「首次進入時載入」— 「從子流程返回且資料已變更」這條進入條件從未被列出。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>「返回時畫面會自己更新」是開發者的隱性假設，且與框架事實相反&lt;/strong>。Flutter 的 push/pop 保留底下頁面的 State，這是框架的明確行為。開發者在桌面瀏覽器思維（每次導航都重新載入）下設計行動 app 的資料流，假設與平台事實錯位。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>await-then-refresh 是點對點補丁，不是收斂&lt;/strong>。修復把三個導航入口包進 &lt;code>_navigateAndRefresh&lt;/code>，解了當下的三個入口 — 但第 N+1 個新入口忘記包 helper 就重演一次。這也是規格層補 BR-10 的原因：把「統計要 reactive」聲明為業務規則，讓資料層變更自動推送到所有訂閱畫面，而不是每個導航點各自記得刷新。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="策略">策略&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>每個顯示衍生資料的畫面固定一問&lt;/strong>：「底層資料在此畫面存活期間變更時，畫面如何得知？」指認不出機制就是缺口。變更來源不只子流程返回，還包括背景同步、其他分頁操作。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>畫面狀態矩陣的進入條件欄必須列出「返回/恢復」&lt;/strong>：「首次進入」和「從子流程返回」是兩個不同的進入條件，後者攜帶「資料可能已變更」的前提。只列首次進入的矩陣就是 happy-path-only。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>架構層收斂優先於導航點補丁&lt;/strong>：衍生統計宣告為 reactive 依賴（&lt;code>ref.watch&lt;/code> 監聽資料層）讓「畫面反映最新資料」成為架構性質而非每個入口的手動責任。過渡期可用 await-then-refresh 止血，但要在規格留下收斂方向（本案的 BR-10），否則補丁會被誤認為終態。&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>happy-path-only 反模式的完整定義 → &lt;a href="https://tarrragon.github.io/blog/ux-design/01-screen-state-machine/anti-pattern-happy-path-only/" data-link-title="反模式：假設使用者只走 happy path" data-link-desc="為什麼開發者容易只設計 happy path 的 UI、使用者在非 happy path 狀態下被困住的機制分析、以及用狀態矩陣系統性地防止這個問題">反模式：只設計 happy path&lt;/a>&lt;/li>
&lt;li>想用狀態矩陣盤點進入條件 → &lt;a href="https://tarrragon.github.io/blog/ux-design/01-screen-state-machine/state-matrix-definition/" data-link-title="畫面狀態矩陣的定義與填寫方法" data-link-desc="四欄矩陣（顯示 / 可用操作 / 進入條件 / 退出路徑）的定義、填寫步驟和檢查規則 — 退出路徑為空 = UX 死胡同">畫面狀態矩陣的定義與填寫方法&lt;/a>&lt;/li>
&lt;li>類似案例（狀態矩陣缺口）→ &lt;a href="https://tarrragon.github.io/blog/ux-design/cases/five-states-zero-exits/" data-link-title="U.C1 Terminal 畫面五個狀態零個退出路徑" data-link-desc="Flutter app 的 Terminal 畫面有 idle/connecting/connected/error/disconnected 五個 enum 狀態，每個狀態都沒有 back 或 disconnect 按鈕 — 使用者一旦進入就出不去">U.C1 五狀態零退出&lt;/a>&lt;/li>
&lt;li>BR-10 的完整技術收斂（導航補償 → EventBus 橋接 → repository 觀測出口）→ &lt;a href="https://tarrragon.github.io/blog/work-log/flutter_riverpod_reactive_boundary_ref_watch/" data-link-title="加書後統計不刷新 — ref.watch 觀察的是 provider 圖、不是資料庫" data-link-desc="頁面用了 Riverpod 卻在資料寫入後不更新、或發現自己在導航返回點補 loadData()、用 EventBus 事件觸發 reload 時使用。ref.watch 的 reactive 範圍是 provider 圖上的狀態變化；資料庫寫入不在圖上，補償刷新的出現就是這個缺口的訊號。">ref.watch 觀察的是 provider 圖、不是資料庫&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>book_overview_app 是一個 Flutter 書庫管理教學專案，以下案例取自該專案的實機測試。happy-path-only 反模式不只出現在導航流、也出現在資料流——畫面設計只回答「進入時顯示什麼」，沒回答「底層資料在畫面存活期間變更時，畫面如何得知」。</p>
<h2 id="觀察">觀察</h2>
<p>book_overview_app 的資料管理頁顯示書庫統計（書籍總數、書庫狀態）。統計只在 <code>initState</code> 的 <code>addPostFrameCallback</code> 呼叫一次 <code>loadData()</code>。</p>
<p>實機測試路徑：資料管理頁（顯示無書目）→ 點「搜尋加書」進入新增流程 → 成功加入一本書 → pop 返回資料管理頁。返回後統計仍顯示無書目 — Flutter 的 push/pop 導航不會 dispose/rebuild 底下頁面的 State，<code>initState</code> 不會再跑一次。使用者要退回首頁再重新進入資料管理頁，統計才更新。</p>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>修復前</th>
          <th>修復後（W1-085）</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>統計載入時機</td>
          <td>僅 <code>initState</code> 一次</td>
          <td>導航返回後再載入一次</td>
      </tr>
      <tr>
          <td>加書後返回</td>
          <td>停留舊值，需回首頁再進入</td>
          <td>立即反映最新統計</td>
      </tr>
      <tr>
          <td>實作</td>
          <td>三個導航入口各自 <code>pushNamed</code></td>
          <td>三個入口收斂為 <code>_navigateAndRefresh</code> helper：<code>await pushNamed(...)</code> 完成後呼叫 <code>viewModel.loadData()</code></td>
      </tr>
  </tbody>
</table>
<p>修復後規格同步新增 BR-10「衍生統計 reactive 一致性」：衍生統計必須響應底層書庫資料變更自動更新（Riverpod <code>ref.watch</code> reactive 監聽），不得依賴頁面重進或手動刷新 — 這是比 await-then-refresh 更徹底的收斂方向。</p>
<h2 id="判讀">判讀</h2>
<ol>
<li>
<p><strong>happy-path-only 反模式的資料版本</strong>。導航版的 happy path 是「使用者只會往前走」，資料版的 happy path 是「畫面顯示的資料在畫面存活期間不會變」。本案的畫面狀態矩陣若有填，「進入條件」欄只會有一條「首次進入時載入」— 「從子流程返回且資料已變更」這條進入條件從未被列出。</p>
</li>
<li>
<p><strong>「返回時畫面會自己更新」是開發者的隱性假設，且與框架事實相反</strong>。Flutter 的 push/pop 保留底下頁面的 State，這是框架的明確行為。開發者在桌面瀏覽器思維（每次導航都重新載入）下設計行動 app 的資料流，假設與平台事實錯位。</p>
</li>
<li>
<p><strong>await-then-refresh 是點對點補丁，不是收斂</strong>。修復把三個導航入口包進 <code>_navigateAndRefresh</code>，解了當下的三個入口 — 但第 N+1 個新入口忘記包 helper 就重演一次。這也是規格層補 BR-10 的原因：把「統計要 reactive」聲明為業務規則，讓資料層變更自動推送到所有訂閱畫面，而不是每個導航點各自記得刷新。</p>
</li>
</ol>
<h2 id="策略">策略</h2>
<ol>
<li>
<p><strong>每個顯示衍生資料的畫面固定一問</strong>：「底層資料在此畫面存活期間變更時，畫面如何得知？」指認不出機制就是缺口。變更來源不只子流程返回，還包括背景同步、其他分頁操作。</p>
</li>
<li>
<p><strong>畫面狀態矩陣的進入條件欄必須列出「返回/恢復」</strong>：「首次進入」和「從子流程返回」是兩個不同的進入條件，後者攜帶「資料可能已變更」的前提。只列首次進入的矩陣就是 happy-path-only。</p>
</li>
<li>
<p><strong>架構層收斂優先於導航點補丁</strong>：衍生統計宣告為 reactive 依賴（<code>ref.watch</code> 監聽資料層）讓「畫面反映最新資料」成為架構性質而非每個入口的手動責任。過渡期可用 await-then-refresh 止血，但要在規格留下收斂方向（本案的 BR-10），否則補丁會被誤認為終態。</p>
</li>
</ol>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>happy-path-only 反模式的完整定義 → <a href="/blog/ux-design/01-screen-state-machine/anti-pattern-happy-path-only/" data-link-title="反模式：假設使用者只走 happy path" data-link-desc="為什麼開發者容易只設計 happy path 的 UI、使用者在非 happy path 狀態下被困住的機制分析、以及用狀態矩陣系統性地防止這個問題">反模式：只設計 happy path</a></li>
<li>想用狀態矩陣盤點進入條件 → <a href="/blog/ux-design/01-screen-state-machine/state-matrix-definition/" data-link-title="畫面狀態矩陣的定義與填寫方法" data-link-desc="四欄矩陣（顯示 / 可用操作 / 進入條件 / 退出路徑）的定義、填寫步驟和檢查規則 — 退出路徑為空 = UX 死胡同">畫面狀態矩陣的定義與填寫方法</a></li>
<li>類似案例（狀態矩陣缺口）→ <a href="/blog/ux-design/cases/five-states-zero-exits/" data-link-title="U.C1 Terminal 畫面五個狀態零個退出路徑" data-link-desc="Flutter app 的 Terminal 畫面有 idle/connecting/connected/error/disconnected 五個 enum 狀態，每個狀態都沒有 back 或 disconnect 按鈕 — 使用者一旦進入就出不去">U.C1 五狀態零退出</a></li>
<li>BR-10 的完整技術收斂（導航補償 → EventBus 橋接 → repository 觀測出口）→ <a href="/blog/work-log/flutter_riverpod_reactive_boundary_ref_watch/" data-link-title="加書後統計不刷新 — ref.watch 觀察的是 provider 圖、不是資料庫" data-link-desc="頁面用了 Riverpod 卻在資料寫入後不更新、或發現自己在導航返回點補 loadData()、用 EventBus 事件觸發 reload 時使用。ref.watch 的 reactive 範圍是 provider 圖上的狀態變化；資料庫寫入不在圖上，補償刷新的出現就是這個缺口的訊號。">ref.watch 觀察的是 provider 圖、不是資料庫</a></li>
</ul>
]]></content:encoded></item></channel></rss>