SKILL.md
readonlyread-only
name
flutter-use-http-package
description
使用 `http` 套件執行 GET、POST、PUT 或 DELETE 請求。當你需要從 REST API 獲取資料或傳送資料時使用。
實作 Flutter 網路連線
目錄
設定與權限
設定環境與平台特定的權限,以允許網路存取。
- 透過終端機新增
http套件依賴:flutter pub add http - 在 Dart 檔案中匯入套件:
import 'package:http/http.dart' as http; - 設定 Android 權限:在
android/app/src/main/AndroidManifest.xml中加入網際網路權限:<uses-permission android:name="android.permission.INTERNET" /> - 設定 macOS 授權:在
macos/Runner/DebugProfile.entitlements與macos/Runner/Release.entitlements中加入網路客戶端金鑰:<key>com.apple.security.network.client</key> <true/>
請求執行與回應處理
執行 HTTP 操作並將回應對應到強型別的 Dart 物件。
- URI: 一律使用
Uri.parse('your_url')解析 URL 字串。 - 標頭: 透過
headers參數對應注入授權與內容類型標頭。使用HttpHeaders.authorizationHeader作為驗證令牌。 - 酬載: 對於 POST 與 PUT 請求,使用
dart:convert的jsonEncode()編碼主體。 - 狀態驗證: 檢查
response.statusCode。將200 OK(GET/PUT/DELETE)與201 CREATED(POST)視為成功。 - 錯誤處理: 對非成功狀態碼拋出明確的例外。失敗時絕不回傳
null,因為這會阻止FutureBuilder觸發錯誤狀態,導致無限載入指示器。 - 反序列化: 使用
jsonDecode(response.body)解析原始字串,並透過工廠建構子(例如fromJson)對應到自訂的 Dart 物件。
背景解析
將耗時的 JSON 解析卸載到獨立的 Isolate,以防止 UI 卡頓(掉幀)。
- 匯入
package:flutter/foundation.dart。 - 使用
compute()函式在背景 Isolate 中執行解析邏輯。 - 確保傳遞給
compute()的解析函式是頂層函式或靜態方法,因為閉包或實例方法無法跨 Isolate 傳遞。
工作流程:執行網路操作
使用以下檢查清單來實作並驗證網路操作。
任務進度:
- [ ] 1. 定義強型別的 Dart 模型,並包含
fromJson工廠建構子。 - [ ] 2. 實作回傳
Future<Model>的網路請求方法。 - [ ] 3. 根據操作類型套用條件邏輯:
- 如果取得資料(GET): 將查詢參數附加到 URI。
- 如果修改資料(POST/PUT): 設定
'Content-Type': 'application/json; charset=UTF-8'並附加jsonEncode主體。 - 如果刪除資料(DELETE): 成功時(
200 OK)回傳空的模型實例。
- [ ] 4. 驗證
statusCode,失敗時拋出Exception。 - [ ] 5. 使用
FutureBuilder將Future整合到 UI 中。 - [ ] 6. 處理
snapshot.hasData、snapshot.hasError,預設顯示CircularProgressIndicator。 - [ ] 7. 回饋迴圈: 執行應用程式 -> 觸發網路請求 -> 檢查主控台是否有未處理的例外 -> 修正解析或權限錯誤。
範例
高擬真實作:在背景取得與解析
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
// 1. 用於 Isolate 的頂層解析函式
List<Photo> parsePhotos(String responseBody) {
final parsed = (jsonDecode(responseBody) as List<Object?>)
.cast<Map<String, Object?>>();
return parsed.map<Photo>(Photo.fromJson).toList();
}
// 2. 含背景解析的網路執行
Future<List<Photo>> fetchPhotos() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/photos'),
headers: {
HttpHeaders.authorizationHeader: 'Bearer your_token_here',
HttpHeaders.acceptHeader: 'application/json',
},
);
if (response.statusCode == 200) {
// 將耗時的解析卸載到背景 Isolate
return compute(parsePhotos, response.body);
} else {
throw Exception('無法載入照片。狀態碼:${response.statusCode}');
}
}
// 3. 強型別模型
class Photo {
final int id;
final String title;
final String thumbnailUrl;
const Photo({
required this.id,
required this.title,
required this.thumbnailUrl,
});
factory Photo.fromJson(Map<String, dynamic> json) {
return Photo(
id: json['id'] as int,
title: json['title'] as String,
thumbnailUrl: json['thumbnailUrl'] as String,
);
}
}
// 4. UI 整合
class PhotoGallery extends StatefulWidget {
const PhotoGallery({super.key});
@override
State<PhotoGallery> createState() => _PhotoGalleryState();
}
class _PhotoGalleryState extends State<PhotoGallery> {
late Future<List<Photo>> _futurePhotos;
@override
void initState() {
super.initState();
// 初始化 Future 一次,避免重建時重新取得
_futurePhotos = fetchPhotos();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<List<Photo>>(
future: _futurePhotos,
builder: (context, snapshot) {
if (snapshot.hasData) {
final photos = snapshot.data!;
return ListView.builder(
itemCount: photos.length,
itemBuilder: (context, index) => ListTile(
leading: Image.network(photos[index].thumbnailUrl),
title: Text(photos[index].title),
),
);
} else if (snapshot.hasError) {
return Center(child: Text('錯誤:${snapshot.error}'));
}
// 預設載入狀態
return const Center(child: CircularProgressIndicator());
},
);
}
}






