iT邦幫忙

2025 iThome 鐵人賽

DAY 9
0
自我挑戰組

攜手 AI 從零開始打造一款 Flutter 應用程式系列 第 9

Day 9: 讓資料「回家」- 跨頁面狀態管理入門

  • 分享至 

  • xImage
  •  

前言

大家好,歡迎來到第九天!昨天,我們試著打造了一個具備完整驗證功能的表單頁面,讓 App 從「只能看」進化到了「能夠互動」。但我們也留下了一個懸念:當我們在表單頁按下「儲存」後,資料被印出就消失了,主畫面的列表紋風不動。

這個問題觸及了 App 開發的核心——狀態管理 (State Management)。所謂「狀態」,就是驅動 App 畫面的資料。我們今天的任務,就是學習 Flutter 中最基礎也最重要的一種跨頁面狀態管理方式,讓「新增消費」頁面的資料能夠安全地「回家」,並更新主畫面的列表。

Step 1: 資料無法改變的根源 - StatelessWidget

這裡我們可以往前翻看 Day 4 的文章,有提到有關 StatelessWidget(無狀態元件) 和 StatefulWidget(有狀態元件)這兩個 Widget 的特性。

StatelessWidget 的特性是「不可變 (immutable)」。一旦它被建立,它內部的所有屬性(包括我們的 transactions 列表)都不能再被更改。這就是為什麼我們無法直接新增資料到列表中的原因。

這裡只要將 HomePage 轉換為 StatefulWidget,賦予它「持有並管理可變狀態」的能力,就可以解決了。

Step 2: 將 HomePage 轉為 StatefulWidget

  1. HomePageextends StatelessWidget 改為 extends StatefulWidget
  2. 滑鼠停在有紅色錯誤底線的 StatefulWidget 上,點擊出現的燈泡圖示,選擇「Convert to StatefulWidget」,IDE 就會自動幫你完成重構。
  3. 會建立一個新的 _HomePageState 類別,並將原本 HomePagebuild 方法和 transactions 列表剪下並貼上到這個新類別中。
  4. 將 final 關鍵字移除,讓它成為可變的列表。
// lib/main.dart
import 'package:snapsaver/models/transaction.dart';

// 將 Transaction 類別移到 models/transaction.py 統一管理
// ... MyApp class ...

class HomePage extends StatefulWidget {
  // 因為 HomePage 不再持有狀態,建構子可以再次變回 const
  const HomePage({super.key});

  @Override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  // 將 transactions 列表移動到 State 內部
  List<Transaction> transactions = [
    // ... 我們的假資料 ...
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // ... 原本的 build 內容 ...
    );
  }
}

Step 3: 讓資料踏上歸途 - 修改 Navigator.pop

現在,我們需要修改「新增消費」頁面,讓它在關閉時能夠攜帶資料。Navigator.pop 方法可以接受一個可選的參數,這個參數就是我們要回傳的資料。

// lib/add_transaction_page.dart -> _submitForm method
import 'package:snapsaver/models/transaction.dart';

void _submitForm() {
  if (_formKey.currentState!.validate()) {
    // 1. 根據表單內容,建立一個新的 Transaction 物件
    final newTransaction = Transaction(
      title: _titleController.text,
      amount: double.parse(_amountController.text),
      category: '測試分類', // 暫時寫死
      date: DateTime.now(),
    );

    // 2. 將 newTransaction 作為參數傳入 pop 方法
    Navigator.pop(context, newTransaction);
  }
}

Step 4: 在家門口迎接資料 - async / await

資料已經被送出,HomePage 該如何接收呢?
Navigator.push 方法會回傳一個 FutureFuture 代表一個「未來才會完成的操作結果」。我們可以透過 async / await 語法,來「等待」這個 Future 完成,並取得它回傳的資料。

回到 lib/main.dart,我們需要修改 FloatingActionButtononPressed 行為。

// lib/main.dart -> _HomePageState class

// 建立一個新的 async 方法來處理導航與接收資料
void _navigateToAddTransactionPage() async {
  // 使用 await "等待" AddTransactionPage 關閉並回傳資料
  final result = await Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => const AddTransactionPage()),
  );

  // 當 result 不是 null 時 (代表使用者是儲存而不是直接返回)
  // 我們需要更新狀態
  if (result != null && result is Transaction) {
    // 下一步驟會實作這裡
  }
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    // ...
    floatingActionButton: FloatingActionButton(
      // 將 onPressed 指向我們的新方法
      onPressed: _navigateToAddTransactionPage,
      child: const Icon(Icons.add),
    ),
    // ...
  );
}

Step 5: 更新狀態,刷新畫面 - setState

這是最後,也是最神奇的一步。當我們在 StatefulWidget 中想更新資料並重繪畫面時,必須使用 setState 方法。

setState 會做兩件事:

  1. 執行你傳入的函式(通常是更新資料的程式碼)。
  2. 通知 Flutter:「嘿!狀態變了,請重新執行 build 方法來更新畫面!」

將 Step 4 中留空的 if 區塊補上:

// lib/main.dart -> _HomePageState -> _navigateToAddTransactionPage method

if (result != null && result is Transaction) {
  // 呼叫 setState 來更新 UI
  setState(() {
    // 在這裡更新我們的列表
    // 使用 insert(0, ...) 將新項目加到列表最前面,以便立即看到
    transactions.insert(0, result);
  });
}

至關重要:所有會改變 UI 狀態的資料操作,都必須放在 setState 的回呼函式中!

現在,重啟你的 App。點擊 + 按鈕,新增一筆消費,按下儲存... 見證奇蹟的時刻到了!你會發現,主畫面的列表立刻出現了你剛剛新增的那筆資料!

https://ithelp.ithome.com.tw/upload/images/20250923/20163912N247kbdD5y.png

今日結語

今天我們完成了 App 功能的閉環,實現了真正的資料互動!我們深入了解了 Flutter 狀態管理的基本功:

  1. 認識到 StatelessWidgetStatefulWidget 的核心差異。
  2. 學會將 StatelessWidget 重構為 StatefulWidget 以管理可變狀態。
  3. 掌握了使用 Navigator.pop(context, data) 回傳資料的技巧。
  4. 學會使用 async/await 等待 Navigator.push 的回傳結果。
  5. 理解了 setState 是觸發 StatefulWidget 畫面重建的唯一真理。

我們今天所學的,是 Flutter 中最基礎、但使用最頻繁的「手動」狀態管理模式。

然而,我們的 App 現在有一個新問題:所有新增的資料都只存在於記憶體中。一旦你關閉並重啟 App,它們就會消失。該如何讓資料被永久保存下來呢?

明天,我們將迎來一個重大的里程碑:引入後端服務!我們將學習如何設定 Firebase,並使用 Firestore 雲端資料庫來永久儲存我們的消費紀錄。


上一篇
Day 8: 從「能看」到「能用」- 處理使用者輸入與 Form 表單
下一篇
Day 10: 引入後端大腦 - Firebase 專案設定與使用者驗證
系列文
攜手 AI 從零開始打造一款 Flutter 應用程式12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言