iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
自我挑戰組

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

Day 20: AI 變身理財顧問 - Gemini 的多筆資料分析

  • 分享至 

  • xImage
  •  

前言

大家好,歡迎來到第二十天!昨天,我們成功打通了從拍照到記帳的完整智慧流程,實現了「省錢拍拍」的核心功能。我們的 App 現在已經是一個非常高效的資料輸入工具。

但我們不禁要問:辛苦記帳的目的,僅僅是為了記錄嗎?當然不是。記帳的最終目的是為了從資料中學習,發現我們的消費模式,並做出更好的財務決策。

今天,我們將讓 App 的 AI 大腦再次升級。我們不再滿足於分析單張發票,而是要將使用者最近的一批消費紀錄打包發送給 Gemini,並命令它扮演一位個人理財顧問,為我們產生一份客製化的消費分析報告與省錢建議。

Step 1: 準備 AI 的「食材」- 批次讀取 Firestore 資料

要進行分析,AI 首先需要數據。我們需要在 FirestoreService 中新增一個方法,專門用來獲取使用者最近的 N 筆交易紀錄。這次我們使用 .get() 來做一次性讀取,因為分析報告不需要像主頁列表那樣即時更新。

打開 lib/services/firestore_service.dart,加入新方法 getRecentTransactions

Future<List<Transaction>> getRecentTransactions({
    required String userId,
    int limit = 20, // 預設讀取最近 20 筆
  }) async {
    try {
      final snapshot =
          await _usersCollection
              .doc(userId)
              .collection('transactions')
              .orderBy('date', descending: true)
              .limit(limit)
              .get(); // 使用 .get() 進行一次性讀取

      // 將讀取到的文件使用我們模型的 fromFirestore 方法,轉換為 Transaction 物件列表
      return snapshot.docs
          // 使用 hide 關鍵字 「隱藏」某個套件中的特定類別,以消除衝突。
          .map((doc) => Transaction.fromFirestore(doc))
          .toList();
    } catch (e) {
      print("讀取近期交易失敗: $e");
      return []; // 發生錯誤時回傳空列表
    }
  }

Step 2: 升級 GeminiService - 設計分析型 Prompt

分析型的 Prompt 與擷取型的 Prompt 在思維上完全不同。我們不再是要求 AI 找特定欄位,而是要求它進行歸納、總結和建議。

打開 lib/services/gemini_service.dart,加入新方法 getExpenseAnalysis:

Future<String?> getExpenseAnalysis(List<Transaction> transactions) async {
    if (transactions.length < 5) {
      // 資料太少就沒有分析意義
      return '消費紀錄不足 5 筆,暫時無法進行分析。';
    }

    try {
      final model = GenerativeModel(
        model: 'gemini-1.5-flash-latest',
        apiKey: _apiKey,
      );

      final transactionsText = transactions
          .map(
            (t) =>
                "日期: ${t.date.toIso8601String().substring(0, 10)}, 分類: ${t.category}, 金額: ${t.amount.toInt()}",
          )
          .join('\n');

      final prompt = '''
      你是一位專業且友善的個人理財顧問。
      以下是我最近的一些消費紀錄,格式為 "日期, 分類, 金額"。

      --- 消費紀錄開始 ---
      $transactionsText
      --- 消費紀錄結束 ---

      請根據以上資料,嚴格遵循以下指示,用繁體中文為我提供一份簡短(總字數不超過 200 字)的消費分析:
      1. 分析的開頭,請用一個 emoji 和一句鼓勵的話開始。
      2. 找出我花費最多的前三大消費類別,並用條列式列出。
      3. 根據我的消費習慣,提供一個最直接、最實用的省錢建議。
      4. 結尾請保持正向鼓勵。
      ''';

      final content = [Content.text(prompt)];
      final response = await model.generateContent(content);

      return response.text;
    } catch (e) {
      print('Gemini 分析請求失敗: $e');
      return 'AI 分析失敗,請稍後再試。';
    }
  }
  • 資料格式化:我們將 List<Transaction> 轉換為一個簡潔的純文字字串,方便 AI 閱讀。
  • 分析型 Prompt:這次的 Prompt 更像是在委託一個任務。我們明確地定義了 AI 的角色(理財顧問)、分析的依據(消費紀錄)、以及報告的具體格式和語氣要求。

Step 3: 建立「智慧分析」頁面

我們需要一個新頁面來呈現 AI 生成的分析報告。

  1. 在 lib 資料夾下建立新檔案 analysis_page.dart。
  2. 這個頁面需要是 StatefulWidget,因為它有「載入中」和「載入完成」兩種狀態。
// lib/analysis_page.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:snapsaver/services/firestore_service.dart';
import 'package:snapsaver/services/gemini_service.dart';

class AnalysisPage extends StatefulWidget {
  const AnalysisPage({super.key});
  @override
  State<AnalysisPage> createState() => _AnalysisPageState();
}

class _AnalysisPageState extends State<AnalysisPage> {
  bool _isLoading = true;
  String? _analysisResult;

  final FirestoreService _firestoreService = FirestoreService();
  final GeminiService _geminiService = GeminiService();

  @override
  void initState() {
    super.initState();
    _fetchAndAnalyzeData(); // 頁面一載入就開始分析
  }

  Future<void> _fetchAndAnalyzeData() async {
    final user = FirebaseAuth.instance.currentUser;
    if (user == null) {
      setState(() {
        _isLoading = false;
        _analysisResult = '錯誤:使用者未登入。';
      });
      return;
    }

    // 1. 從 Firestore 獲取資料
    final transactions = await _firestoreService.getRecentTransactions(userId: user.uid);
    if (!mounted) return;

    // 2. 將資料交給 Gemini 分析
    final result = await _geminiService.getExpenseAnalysis(transactions);
    if (!mounted) return;
    
    // 3. 更新 UI
    setState(() {
      _analysisResult = result;
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('智慧消費分析')),
      body: Center(
        child: _isLoading
            ? const CircularProgressIndicator()
            : SingleChildScrollView(
                padding: const EdgeInsets.all(16.0),
                child: Card(
                  elevation: 4.0,
                  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
                  child: Padding(
                    padding: const EdgeInsets.all(20.0),
                    child: Text(
                      _analysisResult ?? '無法產生分析報告。',
                      style: Theme.of(context).textTheme.bodyLarge?.copyWith(height: 1.5),
                    ),
                  ),
                ),
              ),
      ),
    );
  }
}

Step 4: 從主頁面導航到分析頁

最後一步,讓我們把 Day 5 建立的「查看紀錄」按鈕,賦予它新的生命!

打開 lib/main.dart,找到 _HomePageState 中對應的按鈕,為它加上導航功能。

// lib/main.dart
import 'package:snapsaver/analysis_page.dart'; // 導入新頁面

// ... _HomePageState -> build -> 中間功能按鈕區塊 ...

// 找到代表「查看紀錄」的那個 Column
child: GestureDetector(
  onTap: () {
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => const AnalysisPage()),
    );
  },
  child: const Column(
    children: [
      Icon(Icons.bar_chart, size: 40, color: Colors.teal), // 圖示可以換成 bar_chart
      Text('智慧分析'), // 文字也可以更新
    ],
  ),
),
// ...

讓我們重啟 App,確保你已經有好幾筆消費紀錄,然後點擊「智慧分析」按鈕。稍待片刻,你就能看到 Gemini 為你量身打造的專屬消費分析報告了!

https://ithelp.ithome.com.tw/upload/images/20251004/20163912W3UUcR2Vxh.png

今日結語
今天,我們的 AI 夥伴正式從「資料擷取員」晉升為了「理財分析師」。我們成功地:

  1. 學會了從 Firestore 進行批次資料讀取。
  2. 將多筆結構化資料轉換為 AI 可讀的文字格式。
  3. 設計了更複雜的分析型 Prompt,引導 AI 進行歸納與總結。
  4. 建立了一個專門的頁面來呈現 AI 生成的智慧洞察。

「省錢拍拍」不再只是一個被動的記帳工具,它已經開始能主動地為使用者提供價值。

至此,我們已經完成了從 UI 設計、後端整合到 AI 賦能的完整開發旅程。在接下來的鐵人賽最終篇章中,我們將會聚焦於專案的打磨與完善,例如處理更多的錯誤邊界、優化 UI/UX、以及為最終上架做準備。


上一篇
Day 19: 串連一切 - 實現一鍵掃描,智慧填單
系列文
攜手 AI 從零開始打造一款 Flutter 應用程式20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言