iT邦幫忙

2025 iThome 鐵人賽

DAY 14
0
Mobile Development

《30 天 Flutter:跨平台 AI 行程規劃 App》系列 第 14

Day 14 - 從 Prompt 到程式碼:API 串接前的測試與準備

  • 分享至 

  • xImage
  •  

前幾天把 UI 畫面大致刻完了,今天正式進入後端串接的階段!在真正接上 API 之前,會先用假資料(Fake Repository)來測試,確認資料流跟狀態管理架構沒問題。今天的重點是:把需求整理成完整的 Prompt,交給 Gemini Code Assist 自動生成可運行程式碼,讓開發流程更快、更順暢,之後再順利切換到真實 API。

專案資料夾架構

lib/
├─ repositories/             # 資料存取邏輯(抽象層 + 假資料 + 真 API)
│   ├─ trip_repository.dart        # 抽象層 (interface)
│   ├─ fake_trip_repository.dart   # 假資料 (開發/測試用)
│   └─ api_trip_repository.dart    # 真 API (Dio 實作)
├─ providers/                # Riverpod 狀態管理
│   └─ trip_provider.dart
└─ main.dart

這樣劃分的好處是分層清楚,未來換資料來源(假資料 → API)時,只要替換 Repository,UI 和 Provider 幾乎不用動。

為什麼要用抽象層 (Repository Pattern)?

抽象層(Repository interface)定義「我要怎麼存取資料」,但不管資料來源是假資料還是真 API。

好處:

  • 切換方便:開發時先用 FakeTripRepository,API ready 後換成 ApiTripRepository,UI 完全不用改。
  • 測試友好:測試不用打 API,只要用假資料就好。
  • 解耦:UI 不直接依賴 API 實作,未來換掉 Dio 或資料結構也不用重寫 UI。

Repository 命名規則

Repository 一般用「模型名稱 + Repository」,方便維護:

  • TripRepository:行程資料存取
  • ActivityRepository:活動資料存取
  • UserRepository:使用者資料存取

對應檔案:

  • trip_repository.dart:抽象層
  • fake_trip_repository.dart:假資料
  • api_trip_repository.dart:真 API

程式碼範例

抽象層 (trip_repository.dart)

abstract class TripRepository {
  Future<Trip> getTrip({required String locations, required int days});
}

假資料 (fake_trip_repository.dart)

class FakeTripRepository implements TripRepository {
  @override
  Future<Trip> getAITrip({required String locations, required int days}) async {
    await Future.delayed(const Duration(seconds: 1));
    final now = DateTime.now();
    final tripStartDate = DateTime(now.year, now.month, now.day, 9);

    final activities = [
      Activity(
        id: 1,
        type: ActivityType.transport,
        location: "日月潭",
        startTime: tripStartDate,
        duration: const Duration(minutes: 60),
        endTime: tripStartDate.add(const Duration(minutes: 60)),
        transportType: TransportType.other,
        note: "抵達日月潭,前往住宿地點辦理入住手續。",
      ),
    ];

    return Trip(
      id: const Uuid().v4(),
      title: "$locations行程",
      activities: activities,
    );
  }
}

真 API (api_trip_repository.dart)

class ApiTripRepository implements TripRepository {
  final Dio dio;

  ApiTripRepository({required this.dio});

  @override
  Future<Trip> getAITrip({required String locations, required int days}) async {
    try {
      final response = await dio.post(
        'https://your-api-endpoint.com/generate-trip',
        data: {'locations': locations, 'days': days},
      );

      if (response.statusCode == 200) {
        return Trip.fromJson(response.data);
      } else {
        throw Exception('Failed to load trip from API');
      }
    } catch (e) {
      throw Exception('Failed to load trip: $e');
    }
  }
}

Riverpod Provider(trip_provider.dart)

Future<void> getAITrip({required String locations, required int days}) async {
  state = const AsyncValue.loading();
  state = await AsyncValue.guard(() async {
    final trip = await ref
        .read(tripRepositoryProvider)
        .getAITrip(locations: locations, days: days);
    return [trip];
  });
}

AI 自動生成程式碼體驗分享

依照上面的需求,我轉成 Prompt 提供給 Gemini Code Assist 幫我生成可運行程式碼:

請幫我用 Dart + Flutter 寫一個 Trip API 的程式碼。以下是詳細需求:

---

### 功能需求
1. 輸入:
   - locations = 日月潭
   - days = 2
2. 輸出:
   - JSON 格式的完整行程規劃,包含多個 activities,每個 activity 可能有 childActivities
   - JSON 結構如下範例:
{
    "title": "日月潭行程",
    "activities": [
        {
            "type": "transport",
            "location": "日月潭",
            "startTime": "2025-09-10T09:00:00+08:00",
            "duration": 60,
            "endTime": "2025-09-10T10:00:00+08:00",
            "transportType": "other",
            "note": "抵達日月潭,前往住宿地點辦理入住手續。",
            "childActivities": []
        },
        ...
    ]
}

---

### 技術棧與套件
- 語言:Dart + Flutter
- 狀態管理:Riverpod
- HTTP:Dio
- Model:json_serializable

---

### 資料模型
- Trip, Activity, ChildActivity
- Activity 包含 type, location, startTime, duration, endTime, transportType, note, isConfirmed, childActivities
- ChildActivity 包含 id, name, duration, transportType, note

---

### 資料夾架構
lib/
├─ models/
├─ repositories/
├─ providers/

---

### 功能實作要求
1. Repository 三層:TripRepository (抽象) / FakeTripRepository / ApiTripRepository
2. 先用 FakeTripRepository 做測試,再串 ApiTripRepository
3. Riverpod provider 可呼叫 repository 取得 Trip
4. 自動生成 JSON 序列化與反序列化

---

### 注意事項
- 生成完整程式碼,包括 imports
- 命名遵循資料夾與 Model 命名
- 使用 json_serializable 語法糖

從輸入 Prompt 到 Gemini Code Assist 實際生成完整程式碼,整個過程花了 36 分鐘。前 10 分鐘,它就完成了我所有功能的初版,而且整個專案可以順利運行,速度相當驚人。

接下來的 20 分鐘,我主要在檢查和微調程式碼。部分 enum 的內容被刪掉,需要補回來;原本 provider 裡的一些邏輯也不見了,我就逐一修正並測試。最後的 6 分鐘,我花時間告訴它哪些按鈕要觸發 getAITrip,以及畫面的跳轉邏輯等等。

整體來說,效果真的不錯!這份 Prompt 雖然還有優化空間,如果 enum 多給一點,可能就不用花那麼多時間檢查了,也能更方便加入後續測試,避免程式碼被意外刪掉。今天的實測結果很滿意,強烈推薦大家可以試試看!

今日成果

淺色版 深色版

上一篇
Day 13 - 行程拖曳功能開發:LongPressDraggable 與 DragTarget 的選擇與實作
下一篇
Day 15 - 可以和 AI 說話了!從假資料到真實情境完整演練
系列文
《30 天 Flutter:跨平台 AI 行程規劃 App》20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言