Flutter狀態管理大亂鬥:一場尋找最佳方案的冒險旅程
最近碰巧一直在摸Flutter的狀態管理,算是有點被工作還有一些好奇心推著走。之前為了準備線上講座,真的翻了不少資料,也試著整理出一套自己的心得。這次就想順便聊聊當時的一些實作過程跟遇到的雜事,也許會對誰下次做專案挑狀態解法時多少有些參考。
事情大概是這樣,我那個GitHub倉庫其實不是特別規劃得多漂亮,只能說盡量讓它比較容易找到重點吧。有在用的人應該會發現,可能每隔幾天結構又小改一下,大致流程也就是先把暫存狀態(有人叫Ephemeral State?)還有全域那種App State稍微分開討論一下——畢竟兩者本來就常常搞混。
然後,比較關鍵的部分應該就是那些熱門的狀態管理工具啦。像Provider、Riverpod什麼的,每一種我都試著跑了一遍UI,邏輯其實沒有想像中複雜,但要講清楚優缺點,其實很看情境,有些人感覺只在意易用性,有些則偏好彈性高。不過我寫評語都只是個人觀察啦,如果你們覺得哪裡不一樣,很歡迎來討論看看。
流程大概長這樣:先把倉庫怎麼分類、檔案擺法交代一下,接著帶過那幾種基礎狀態,再來才逐步拆解各個第三方套件,用比較直覺的方法展示它們怎麼融進畫面裡。至於每個方案到底適不適合自己手上的專案,好像沒有所謂絕對標準,只能說大家選擇時多留意自己的需求和習慣吧。
總之也沒什麼華麗開場,直接開始好了——
事情大概是這樣,我那個GitHub倉庫其實不是特別規劃得多漂亮,只能說盡量讓它比較容易找到重點吧。有在用的人應該會發現,可能每隔幾天結構又小改一下,大致流程也就是先把暫存狀態(有人叫Ephemeral State?)還有全域那種App State稍微分開討論一下——畢竟兩者本來就常常搞混。
然後,比較關鍵的部分應該就是那些熱門的狀態管理工具啦。像Provider、Riverpod什麼的,每一種我都試著跑了一遍UI,邏輯其實沒有想像中複雜,但要講清楚優缺點,其實很看情境,有些人感覺只在意易用性,有些則偏好彈性高。不過我寫評語都只是個人觀察啦,如果你們覺得哪裡不一樣,很歡迎來討論看看。
流程大概長這樣:先把倉庫怎麼分類、檔案擺法交代一下,接著帶過那幾種基礎狀態,再來才逐步拆解各個第三方套件,用比較直覺的方法展示它們怎麼融進畫面裡。至於每個方案到底適不適合自己手上的專案,好像沒有所謂絕對標準,只能說大家選擇時多留意自己的需求和習慣吧。
總之也沒什麼華麗開場,直接開始好了——
我的GitHub倉庫這樣整理讓專案結構一目了然
一開始,整理 GitHub 資料夾這件事,好像沒什麼標準答案。大致上,有人會把資料、模型什麼的分成好幾個小區塊。比如:有個擺靜態食譜資料的地方──可能叫 data 資料夾;然後又弄了個 models,專門放那種用來描述或規範食譜內容的模型檔案。接著,大概還會劃出 presentation 這一區,裡頭再拆兩層:有 pages,也就是整頁的食譜畫面;widgets 則留給像 ListTile 這種比較小型、只負責單筆顯示的小元件。至於 repository,那通常是集中管理抓取一串食譜資料背後邏輯的位置。
其實,每次開新主題都多少要自我提醒一下——怎樣才不會搞混?畢竟等下就要碰一些可能讓人頭暈腦脹的東西。如果說到行動裝置 App 的介面變化,常見那種「一下子換掉」的畫面狀態,在 Flutter 裡頭有時候被叫做短暫狀態(Ephemeral State)。而另外一種狀況,好像更常發生在不同頁面間共用某些數據,比如書籤影片,不論你在個人檔案還是滑到底部彈出的書籤清單,都能看到同樣內容,這類共享資訊通常被歸類為 App 狀態。
有時候也會冒出點困惑:「欸,如果兩三個畫面都要拿同一份資料,是不是得想辦法讓大家都抓得到?」畢竟同步起來不太容易,要是沒有設計好就亂套了。舉例來說,有人或許注意過,一些應用程式裡頭,不同頁面的某項紀錄彼此互通,但到底該怎麼安排才能讓多處 UI 都能讀到同一塊訊息?嗯...這問題看起來不少人曾經卡住過吧。不一定每次都有最理想解法,只能說得視情況微調試試看罷了。
其實,每次開新主題都多少要自我提醒一下——怎樣才不會搞混?畢竟等下就要碰一些可能讓人頭暈腦脹的東西。如果說到行動裝置 App 的介面變化,常見那種「一下子換掉」的畫面狀態,在 Flutter 裡頭有時候被叫做短暫狀態(Ephemeral State)。而另外一種狀況,好像更常發生在不同頁面間共用某些數據,比如書籤影片,不論你在個人檔案還是滑到底部彈出的書籤清單,都能看到同樣內容,這類共享資訊通常被歸類為 App 狀態。
有時候也會冒出點困惑:「欸,如果兩三個畫面都要拿同一份資料,是不是得想辦法讓大家都抓得到?」畢竟同步起來不太容易,要是沒有設計好就亂套了。舉例來說,有人或許注意過,一些應用程式裡頭,不同頁面的某項紀錄彼此互通,但到底該怎麼安排才能讓多處 UI 都能讀到同一塊訊息?嗯...這問題看起來不少人曾經卡住過吧。不一定每次都有最理想解法,只能說得視情況微調試試看罷了。
Comparison Table:
狀態管理方式 | 優點 | 缺點 | 適用場景 |
---|---|---|---|
Provider | 簡單易上手,良好的延展性,適合小型與大型專案 | 依賴注入不便,難以獨立管理依賴關係 | 適合一般應用程式的狀態管理 |
BLoC | 清晰的邏輯結構,方便測試功能,能有效抓取錯誤 | 學習門檻較高,過多模板代碼可能影響開發效率 | 適合需要複雜狀態管理和測試的專案 |
GetX | 直觀易懂,上手快,編寫更少程式碼,多功能支持依賴注入和路由控制 | 社群維護相對閉鎖,有人覺得參與感低 | 適合快速開發、對語法要求不高的小工具或應用 |
InheritedWidget (基礎機制) | 提高元件更新效率,是 Flutter 設計哲學的一部分 | 需理解其背後運作,不易掌握 | 所有主流狀態管理方式的基礎 |

先搞懂瞬態狀態和應用狀態的差別再談管理
Flutter 有關狀態管理的方式其實不少,不少人剛開始碰到這個框架時,往往就會先遇到 setState() 這個東西。大概每個新手都經歷過那種覺得 setState() 很方便、很神奇的時期吧?畢竟 UI 跟著資料變動這件事,本來就是大家最直觀想解決的需求。說起來,setState() 其實來自 StatefulWidget,書上——像是 Flutter Apprentice 那類的——對它有蠻多說明。聽說 StatefulWidget 的特點是可以保留一些變動資料,這在需要即時變更畫面內容時還蠻受用的。有興趣深究細節的人,也許會去瞄一下官方文件,不過有些人可能看完也只是略懂皮毛。
要真的動手寫嘛,好像也沒什麼大道理,就是分幾步驟而已。例如第一步,大概就是把畫面裡會變動的那些狀態先設計好。關於 setState 的邏輯…咦,好像一下子又扯遠了。不過很多人第一次用 setState 時,應該多少都有種「啊原來只要呼叫它,就能觸發 UI 重繪」的體驗吧?但其實背後到底發生什麼,有些初學者搞不太清楚也是常見事。
總之,每次想到 setState 就忍不住回頭思考,那些簡單小功能,到底是靠哪些機制撐起來的。有時候想多了反而混亂,但如果只是為了讓畫面即時更新,多半還是從 setState 開始摸索比較常見啦。
要真的動手寫嘛,好像也沒什麼大道理,就是分幾步驟而已。例如第一步,大概就是把畫面裡會變動的那些狀態先設計好。關於 setState 的邏輯…咦,好像一下子又扯遠了。不過很多人第一次用 setState 時,應該多少都有種「啊原來只要呼叫它,就能觸發 UI 重繪」的體驗吧?但其實背後到底發生什麼,有些初學者搞不太清楚也是常見事。
總之,每次想到 setState 就忍不住回頭思考,那些簡單小功能,到底是靠哪些機制撐起來的。有時候想多了反而混亂,但如果只是為了讓畫面即時更新,多半還是從 setState 開始摸索比較常見啦。
從setState開始認識最基礎的狀態更新方法
一開始有提到要在初始化時載入資料,不過這個步驟,感覺上其實沒什麼太多特別技巧,就是那種小型專案常見的做法。然後,好像還有說到要寫個可以更新畫面的函數,但怎麼用、何時呼叫,大家的習慣都不太一樣,有些人會放在狀態變動之後。
說到setState()這種東西,其實給人的印象蠻分歧。有些開發者覺得它簡單明瞭,特別是遇到比較小的應用程式,直接下去用,整體看起來也順眼,而且好像也不用額外安裝什麼套件。可是,如果今天專案規模大了一點,那就不太一樣了——有人會發現狀態管理變得有點麻煩,也不是那麼容易追蹤。更不用說,如果不停地調用setState(),感覺代碼會越寫越亂,到最後自己也搞不清楚哪裡有改到畫面。
還有啊,用這種方式,其實只侷限在單一元件內部而已,要是想讓不同元件共用資料,那難度就上升不少。這些好壞處,大概就是現在很多初學者或剛接觸Flutter的人會討論的重點。不過話又說回來,如果只是想試著快速把東西做出來、或者專案真的很小,也許setState()還算是能接受的方法吧。至於那些進階需求,就可能得考慮其他方案了。
說到setState()這種東西,其實給人的印象蠻分歧。有些開發者覺得它簡單明瞭,特別是遇到比較小的應用程式,直接下去用,整體看起來也順眼,而且好像也不用額外安裝什麼套件。可是,如果今天專案規模大了一點,那就不太一樣了——有人會發現狀態管理變得有點麻煩,也不是那麼容易追蹤。更不用說,如果不停地調用setState(),感覺代碼會越寫越亂,到最後自己也搞不清楚哪裡有改到畫面。
還有啊,用這種方式,其實只侷限在單一元件內部而已,要是想讓不同元件共用資料,那難度就上升不少。這些好壞處,大概就是現在很多初學者或剛接觸Flutter的人會討論的重點。不過話又說回來,如果只是想試著快速把東西做出來、或者專案真的很小,也許setState()還算是能接受的方法吧。至於那些進階需求,就可能得考慮其他方案了。

Provider套件如何用觀察者模式簡化狀態共享
Flutter 裡頭的 Provider,其實有點像觀察者模式,但說是完全一樣好像又差那麼一點。ChangeNotifier 這個東西,大約是負責管理狀態跟發布通知的角色,聽說它只要呼叫 notifyListeners(),畫面底下那些想更新的人就會知道。不過大部分人碰到 ChangeNotifierProvider 這玩意兒時,可能還得花點時間理解,它有時候像是在 UI 樹底下偷偷傳遞資料給某些後代元件。Consumer 則比較像站在旁邊等著狀態變動,好像快要輪到它表演時就會自己重建畫面。
講到怎麼寫 provider,大致上得先弄個繼承自 ChangeNotifier 的類別吧?印象中這步驟很多教學都有提過,不過細節偶爾會有人忘記哪裡要加 notifyListeners()——沒有那步通常 UI 不會理你。然後,有的人喜歡一開始就把所有需要的狀態都丟進去,也有人是用到才加進來。方法多半就是定義幾個欄位,然後當數值被改動,適時叫一下 notifyListeners(),希望 UI 能跟著同步。
其實整個流程看起來不算複雜,只是剛開始摸索的時候,很容易忘記一些小地方,比如說 Consumer 要包在哪個 Widget 上、或是哪裡漏了狀態初始化之類。Provider 這種設計,在 Flutter 社群裡頭感覺用的人不少,但每次真的上手,大概還是得反覆測試幾回合才比較順手。有些朋友分享,他們試過各式各樣組合,用法也沒絕對標準。有些專案規模比較大的話,好像還會配合其他架構一起搞。
講到怎麼寫 provider,大致上得先弄個繼承自 ChangeNotifier 的類別吧?印象中這步驟很多教學都有提過,不過細節偶爾會有人忘記哪裡要加 notifyListeners()——沒有那步通常 UI 不會理你。然後,有的人喜歡一開始就把所有需要的狀態都丟進去,也有人是用到才加進來。方法多半就是定義幾個欄位,然後當數值被改動,適時叫一下 notifyListeners(),希望 UI 能跟著同步。
其實整個流程看起來不算複雜,只是剛開始摸索的時候,很容易忘記一些小地方,比如說 Consumer 要包在哪個 Widget 上、或是哪裡漏了狀態初始化之類。Provider 這種設計,在 Flutter 社群裡頭感覺用的人不少,但每次真的上手,大概還是得反覆測試幾回合才比較順手。有些朋友分享,他們試過各式各樣組合,用法也沒絕對標準。有些專案規模比較大的話,好像還會配合其他架構一起搞。
BLoC設計模式就像物理學的作用與反作用定律
進到畫面裡,如果要用 Provider,照理來說第一步通常得把 ChangeNotifierProvider 當成回傳的小元件,不過有些人會在想是不是能先不用急著寫那個。然後大致上還是得準備一個 RecipeProvider 的例項啦,這樣它底下的東西都能取得相關資料,這算是滿常見的作法。再來就有 Consumer 這顆 Widget,反正它就是等著監聽被提供出來的狀態,一旦改變就會重建畫面。有時候看起來複雜,其實核心流程大概就是這幾步。
至於最後一步,大抵也不脫根據 provider 狀態去切換不同內容顯示,有點像是在某些情境下才換頁面的感覺。不過如果真要講優點,大概可以說這東西蠻好裝進現有專案裡,用起來也不太難上手,好像只要摸熟三種主要概念(ChangeNotifier、ChangeNotifierProvider 跟 Consumer)差不多就夠了。規模小的大型的專案都有人用,看起來延展性也還行。
當然了,有些地方可能沒那麼方便,例如 create 這方法會直接寫在 widget 裡頭,聽說因此很難抽出去單獨管理依賴關係。有的人覺得少了依賴注入,就不能隨心所欲地整理物件之類的,所以在一些場景下也許會有限制。不過整體而言,就是一種相對簡單易懂,但偶爾碰到特殊需求還是要評估一下適合度的方法吧。
至於最後一步,大抵也不脫根據 provider 狀態去切換不同內容顯示,有點像是在某些情境下才換頁面的感覺。不過如果真要講優點,大概可以說這東西蠻好裝進現有專案裡,用起來也不太難上手,好像只要摸熟三種主要概念(ChangeNotifier、ChangeNotifierProvider 跟 Consumer)差不多就夠了。規模小的大型的專案都有人用,看起來延展性也還行。
當然了,有些地方可能沒那麼方便,例如 create 這方法會直接寫在 widget 裡頭,聽說因此很難抽出去單獨管理依賴關係。有的人覺得少了依賴注入,就不能隨心所欲地整理物件之類的,所以在一些場景下也許會有限制。不過整體而言,就是一種相對簡單易懂,但偶爾碰到特殊需求還是要評估一下適合度的方法吧。

GetX不只管狀態還自帶路由和依賴注入超能力
一開始的時候,大家通常會從很淺顯的地方著手,也不算什麼大突破。但隨著時間往前推進,可能慢慢就會碰到有點難度、甚至讓人覺得複雜的主題。倒也不是說一下子就全搞懂,只是那種漸進式學習,好像常見於不少領域。
BLoC 這個管理畫面狀態的方法,其實和某些設計模式扯上邊。有些人還愛拿牛頓第三運動定律來做比喻——每次你丟出一個動作,大致都會冒出一種反饋,有點像互相拉扯。這觀念說起來挺抽象,但在程式裡頭,感覺還蠻直觀。
至於真正動手實作時,不太可能一次就全部搞完。最先要處理的大概就是畫面那些變化的狀態吧。有經驗的人通常會先弄個 class 來專門描述 UI 狀態,那細節怎麼寫倒是見仁見智。有時候記得,有時候又混亂成一團。
接著,是去定義那些能夠影響介面的事件,名稱上叫「UI 事件」,其實背後就是一些觸發器。這部分偶爾會跟上一階段內容糾結在一起,也有人覺得拆開比較清楚。不過該有什麼動作還是跑不掉。
然後才輪到 BLoC 本體。它負責接受事件(或說訊號),再視情況回傳或發射對應的新狀態。大家用法多少有差,但核心精神差不多——即使過程偶有卡關,好像也沒辦法一步到位。
等邏輯都架好,回頭把 BLoC 塞進畫面流程裡。一般會先考慮怎樣包裝比較順手,比如用個 bloc provider 把主要 widget 給圍起來;但環境不同做法也未必雷同,有些案子甚至加了額外包層。不知是不是所有人都遇過,不過這種嵌套確實很常見……
BLoC 這個管理畫面狀態的方法,其實和某些設計模式扯上邊。有些人還愛拿牛頓第三運動定律來做比喻——每次你丟出一個動作,大致都會冒出一種反饋,有點像互相拉扯。這觀念說起來挺抽象,但在程式裡頭,感覺還蠻直觀。
至於真正動手實作時,不太可能一次就全部搞完。最先要處理的大概就是畫面那些變化的狀態吧。有經驗的人通常會先弄個 class 來專門描述 UI 狀態,那細節怎麼寫倒是見仁見智。有時候記得,有時候又混亂成一團。
接著,是去定義那些能夠影響介面的事件,名稱上叫「UI 事件」,其實背後就是一些觸發器。這部分偶爾會跟上一階段內容糾結在一起,也有人覺得拆開比較清楚。不過該有什麼動作還是跑不掉。
然後才輪到 BLoC 本體。它負責接受事件(或說訊號),再視情況回傳或發射對應的新狀態。大家用法多少有差,但核心精神差不多——即使過程偶有卡關,好像也沒辦法一步到位。
等邏輯都架好,回頭把 BLoC 塞進畫面流程裡。一般會先考慮怎樣包裝比較順手,比如用個 bloc provider 把主要 widget 給圍起來;但環境不同做法也未必雷同,有些案子甚至加了額外包層。不知是不是所有人都遇過,不過這種嵌套確實很常見……
每種方案都有優缺點關鍵在於找到適合的情境
有時候在 Flutter 裡面,你會看到有人創建一個 bloc 實例,然後丟進去那種叫做 FetchRecipe 的事件。接著畫面上就出現 BlocBuilder 這個 Widget,平常它就是等著狀態一換、自己動手刷新畫面。這樣的寫法讓程式裡面的邏輯跟介面分得還算清楚,不容易搞混。至於狀態嘛,有結構地管理下來,行為也比較不容易亂掉。
不過話說回來,這套方式的學習門檻感覺比一些簡單方案高了一些,不小心會多寫不少模板類型的東西。如果只是想做點小工具或試水溫,整個架構反倒可能讓人覺得複雜。不像某些輕量級解法那麼直覺,但如果要測試功能倒是滿方便的,有人說大概能把錯誤抓出來的機會提升許多。
然後講到 Getx,那又是另一種處理方式。有開發者習慣先寫一個 RecipeController,通常繼承自 GetxController。接下來,他們會宣告一些看起來挺像 UI 狀態的變數——這些變數基本上都是可觀察(observable)的,只要變了什麼東西,好像畫面也能即時反映出差異。
整體用下來,各有各的強項和限制,在不同場景之下選擇哪一種,其實沒有標準答案。有時候甚至只因為團隊之前用習慣、或者某段時間內流行度較高,就決定了。

原來所有狀態管理套件底層都是繼承這個神奇Widget
有時候,大家在螢幕上用 GetX,其實流程看起來沒幾個步驟。像是先建立一個 RecipeController,然後好像還得註冊進 Get 裡面。大部分人會說這動作不算麻煩,也就幾行而已。
過了那麼一會兒,通常接下來就是讓 Controller 去抓一下食譜,大約算是呼叫 fetchRecipes 吧,不會太複雜。等資料回來,再弄個類似 reactive widget 的東西,只要狀態一動它自己就重畫,感覺還挺方便。
最後,有些人習慣根據 Controller 裡頭的數值去切換顯示的內容——也許正在載入、也許資料回傳失敗,都能隨著狀態不同切換元件,看起來挺彈性的。
說到優點,好像不少人覺得 GetX 用起來頗直觀,語法沒什麼壓力,新手摸索個把小時大致都能順利上手。不過當然,每種工具都有適合或不適合的地方,就這樣吧。
過了那麼一會兒,通常接下來就是讓 Controller 去抓一下食譜,大約算是呼叫 fetchRecipes 吧,不會太複雜。等資料回來,再弄個類似 reactive widget 的東西,只要狀態一動它自己就重畫,感覺還挺方便。
最後,有些人習慣根據 Controller 裡頭的數值去切換顯示的內容——也許正在載入、也許資料回傳失敗,都能隨著狀態不同切換元件,看起來挺彈性的。
說到優點,好像不少人覺得 GetX 用起來頗直觀,語法沒什麼壓力,新手摸索個把小時大致都能順利上手。不過當然,每種工具都有適合或不適合的地方,就這樣吧。
最後來聊聊你最愛用哪種狀態管理方式
說到 GetX,這東西用起來好像真的還蠻省事的。它不只是在狀態管理這邊寫得簡單,甚至在依賴注入、路由等等都有支援——有時候寫一寫,好像比 Provider 或 Bloc 這類方案要少不少程式碼。只是,聽人家聊過,GetX 的社群維護也許沒那麼開放?偶爾會有人覺得參與感比較低。
然後其實差不多所有主流 Flutter 狀態管理方式,不管是 Provider、Bloc 還是其他的,他們背後大致上都靠著一個叫 InheritedWidget 的機制。很多情境下,它能讓畫面上的元件更新變得很有效率,也算是 Flutter 內建的一種設計哲學吧。不過大家在挑選時還是各有偏好,有的人可能就是喜歡 Bloc 那種明確流程,有些則愛 Provider 的簡潔。
順帶一提,有些工具包除了處理狀態,也會把依賴注入和元件溝通一起考慮進去,用起來整體感受就更方便。不過嘛,哪一套適合誰,大概每個人心裡都有自己的答案。如果你剛好有什麼特別偏好的做法,也可以分享一下心得給大家參考。
對了,如果覺得這篇內容稍微有幫助,不妨追蹤一下,未來應該還會陸續分享一些跟 Flutter 有關的小經驗。有空也記得留言討論啦~至於支持的話,看心情鼓勵一下也是不錯的!
然後其實差不多所有主流 Flutter 狀態管理方式,不管是 Provider、Bloc 還是其他的,他們背後大致上都靠著一個叫 InheritedWidget 的機制。很多情境下,它能讓畫面上的元件更新變得很有效率,也算是 Flutter 內建的一種設計哲學吧。不過大家在挑選時還是各有偏好,有的人可能就是喜歡 Bloc 那種明確流程,有些則愛 Provider 的簡潔。
順帶一提,有些工具包除了處理狀態,也會把依賴注入和元件溝通一起考慮進去,用起來整體感受就更方便。不過嘛,哪一套適合誰,大概每個人心裡都有自己的答案。如果你剛好有什麼特別偏好的做法,也可以分享一下心得給大家參考。
對了,如果覺得這篇內容稍微有幫助,不妨追蹤一下,未來應該還會陸續分享一些跟 Flutter 有關的小經驗。有空也記得留言討論啦~至於支持的話,看心情鼓勵一下也是不錯的!
