前幾天把 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 interface)定義「我要怎麼存取資料」,但不管資料來源是假資料還是真 API。
好處:
Repository 一般用「模型名稱 + Repository」,方便維護:
對應檔案:
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];
});
}
依照上面的需求,我轉成 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 多給一點,可能就不用花那麼多時間檢查了,也能更方便加入後續測試,避免程式碼被意外刪掉。今天的實測結果很滿意,強烈推薦大家可以試試看!
淺色版 | 深色版 |
---|---|
![]() |
![]() |