今天的重點是把 資料模型 跟 狀態管理 串起來,然後透過 UI 驗證 時間區塊防呆設計,確認資料流與使用流程是否順暢。今天主要目標頁面:
這些頁面都會跟資料模型緊密結合,UI 的變動、資料流的更新都能同步反映。
先把資料與畫面元件整理好,方便維護與擴充:
lib/
├─ models/ # 資料模型(用 json_serializable 生成)
│ ├─ trip.dart
│ ├─ activity.dart
│ └─ child_activity.dart
└─ views/
└─ activity_editor/ # 行程編輯相關頁面
└─ activity_editor_view.dart
└─ child_activity_form.dart
└─ trip_detail_view.dart # 行程總覽頁面
清楚區分資料模型與畫面元件,維護起來更直覺。
我們使用 json_serializable 自動生成資料模型,避免手動轉換 JSON,提高開發效率。用到套件:
json_annotation
:標記需要轉換的屬性json_serializable
:生成 fromJson
/ toJson
build_runner
:執行生成命令,產生 .g.dart
範例:
part 'trip.g.dart';
@JsonSerializable(explicitToJson: true)
class Trip {
final String id;
final String title;
final List<Activity> activities;
Trip({required this.id, required this.title, required this.activities});
Trip copyWith({String? id, String? title, List<Activity>? activities}) {
return Trip(
id: id ?? this.id,
title: title ?? this.title,
activities: activities ?? this.activities,
);
}
factory Trip.fromJson(Map<String, dynamic> json) => _$TripFromJson(json);
Map<String, dynamic> toJson() => _$TripToJson(this);
}
.g.dart
生成指令:
flutter pub run build_runner build --delete-conflicting-outputs
每次修改 model 都要重新執行。
把 model 與 UI 串起來的重點就是 Riverpod。
流程:
Consumer
或 ref.watch
監聽資料主要套件:
flutter_riverpod
:核心狀態管理框架,提供 Provider、Notifier、AsyncNotifier示範:
class TripListNotifier extends AsyncNotifier<List<Trip>> {
@override
FutureOr<List<Trip>> build() => [];
Future<void> addTrip(Trip newTrip) async { /*...*/ }
Future<void> removeTrip(String tripId) async { /*...*/ }
}
final tripListProvider = AsyncNotifierProvider<TripListNotifier, List<Trip>>(
() => TripListNotifier(),
);
用 when
處理不同狀態:
ref.watch(tripListProvider).when(
data: (trips) => ListView.builder(
itemCount: trips.length,
itemBuilder: (context, index) => Text(trips[index].title),
),
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('發生錯誤:$err'),
);
這樣不管 model 變動或資料異步載入,UI 都能正確呈現。
為了提升使用者體驗,時間區塊有兩項優化:
系統檢查輸入時間,確保:
防呆設計 | 時間計算 |
---|---|
![]() |
![]() |
子行程添加 | 行程頁展示 | 今日成果 |
---|---|---|
![]() |
![]() |
![]() |