flutter-use-http-package

flutter-use-http-package

熱門

使用 `http` 套件執行 GET、POST、PUT 或 DELETE 請求。適用於需要從 REST API 取得資料或將資料傳送至 REST API 的情境。

2783星標
163分支
更新於 2026/8/5
SKILL.md
唯讀
名稱
flutter-use-http-package
描述

使用 `http` 套件執行 GET、POST、PUT 或 DELETE 請求。適用於需要從 REST API 取得資料或將資料傳送至 REST API 的情境。

實作 Flutter 網路功能

目錄

環境設定與權限

設定網路存取所需的環境與各平台專屬權限。

  1. 透過終端機新增 http 套件相依性:
    flutter pub add http
    
  2. 在 Dart 檔案中匯入該套件:
    import 'package:http/http.dart' as http;
    
  3. 設定 Android 權限:將網路存取權限新增至 android/app/src/main/AndroidManifest.xml
    <uses-permission android:name="android.permission.INTERNET" />
    
  4. 設定 macOS entitlements:將網路用戶端 key 新增至 macos/Runner/DebugProfile.entitlementsmacos/Runner/Release.entitlements
    <key>com.apple.security.network.client</key>
    <true/>
    

執行請求與回應處理

執行 HTTP 操作,並將回應資料對映至強型別(strongly typed)的 Dart 物件。

  • URI: 務必使用 Uri.parse('your_url') 解析 URL 字串。
  • 標頭(Headers): 透過 headers 參數 Map 帶入驗證資訊與 content-type 標頭。驗證 Token 請使用 HttpHeaders.authorizationHeader
  • Payload: 針對 POST 與 PUT 請求,請使用 dart:convert 中的 jsonEncode() 對 request body 進行編碼。
  • 狀態碼驗證: 檢查 response.statusCode。將 200 OK(GET/PUT/DELETE)與 201 CREATED(POST)視為成功。
  • 錯誤處理: 當狀態碼非成功時,請拋出明確的例外(Exception)。失敗時切勿回傳 null,否則會導致 FutureBuilder 無法觸發錯誤狀態,進而引發無限載入指示器。
  • 反序列化: 使用 jsonDecode(response.body) 解析原始字串,並透過具名建構子(例如 fromJson)將其對映至自訂的 Dart 物件。

背景解析

將高耗能的 JSON 解析作業移至獨立的 Isolate 處理,以避免 UI 卡頓或掉幀。

  • 匯入 package:flutter/foundation.dart
  • 使用 compute() 函式在背景 Isolate 中執行解析邏輯。
  • 確保傳遞給 compute() 的解析函式為頂層函式(top-level function)或靜態方法(static method),因為閉包(closure)或實例方法(instance method)無法跨 Isolate 傳遞。

工作流程:執行網路操作

使用以下檢核表來實作並驗證網路操作。

任務進度:

  • [ ] 1. 定義具備 fromJson 建構子的強型別 Dart 模型。
  • [ ] 2. 實作回傳 Future<Model> 的網路請求方法。
  • [ ] 3. 根據操作類型套用條件邏輯:
    • 若是取得資料 (GET): 將查詢參數(query parameters)附加至 URI。
    • 若是修改資料 (POST/PUT): 設定 'Content-Type': 'application/json; charset=UTF-8' 並附上經 jsonEncode 編碼的 body。
    • 若是刪除資料 (DELETE): 成功 (200 OK) 時回傳空模型實例。
  • [ ] 4. 驗證 statusCode,若失敗則拋出 Exception
  • [ ] 5. 在 UI 中使用 FutureBuilder 整合該 Future
  • [ ] 6. 處理 snapshot.hasDatasnapshot.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('Failed to load photos. Status: ${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,避免元件重新構建(rebuild)時重複觸發請求
    _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('Error: ${snapshot.error}'));
        }
        
        // 預設載入中狀態
        return const Center(child: CircularProgressIndicator());
      },
    );
  }
}