flutter-use-http-package について
Flutter アプリで `http` パッケージを使って通信を実装するためのスキルです。プラットフォーム権限、型付き JSON 処理、エラー対応、バックグラウンド解析を扱います。
主な機能
- 01HTTP 設定とプラットフォーム権限
- 02GET、POST、PUT、DELETE のパターン
- 03型付き JSON のデシリアライズ
- 04compute() によるバックグラウンド解析
- 05FutureBuilder のエラー処理
活用シーン
- 01Flutter アプリへの API リクエスト追加
- 02UI スレッド外での大きな JSON の解析
- 03リクエストや解析の失敗を UI に表示
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ flutter/ agent-plugins --skill flutter-use-http-packageインストール後、Claude Code で/flutter-use-http-packageを入力して読み込まれたか確認します。
詳細
Flutter ネットワーキングの実装
目次
設定と権限
ネットワークアクセスに必要な環境とプラットフォーム固有の権限を設定します。
- ターミナルから
httpパッケージの依存関係を追加します。flutter pub add http - Dart ファイルにパッケージをインポートします。
import 'package:http/http.dart' as http; android/app/src/main/AndroidManifest.xmlにインターネット権限を追加して、Android の権限を設定します。<uses-permission android:name="android.permission.INTERNET" />macos/Runner/DebugProfile.entitlementsとmacos/Runner/Release.entitlementsの両方にネットワーククライアントのキーを追加して、macOS のエンタイトルメントを設定します。<key>com.apple.security.network.client</key> <true/>
リクエストの実行とレスポンスの処理
HTTP 操作を実行し、レスポンスを型安全な Dart オブジェクトにマッピングします。
- URI: URL 文字列は必ず
Uri.parse('your_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 オブジェクトにマッピングします。
バックグラウンドでの解析
UI のジャンク(フレーム落ち)を防ぐため、負荷の高い JSON 解析を別の Isolate に委譲します。
package:flutter/foundation.dartをインポートします。compute()関数を使用して、解析ロジックをバックグラウンドの isolate で実行します。compute()に渡す解析関数は、トップレベル関数または静的メソッドにしてください。クロージャーやインスタンスメソッドは isolate 間で渡せません。
ワークフロー:ネットワーク操作の実行
以下のチェックリストを使って、ネットワーク操作を実装し、検証します。
タスクの進捗:
- 1.
fromJsonファクトリーコンストラクターを持つ、型安全な Dart モデルを定義します。 - 2.
Future<Model>を返すネットワークリクエストメソッドを実装します。 - 3. 操作の種類に応じた条件分岐を適用します。
- データを取得する場合(GET): URI にクエリパラメーターを追加します。
- データを変更する場合(POST/PUT):
'Content-Type': 'application/json; charset=UTF-8'を設定し、jsonEncodeしたボディを指定します。 - データを削除する場合(DELETE): 成功時(
200 OK)に空のモデルインスタンスを返します。
- 4.
statusCodeを検証し、失敗時にはExceptionをスローします。 - 5.
FutureをFutureBuilderを使用して 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. Top-level parsing function for 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. Network execution with background parsing
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) {
// Offload heavy parsing to a background isolate
return compute(parsePhotos, response.body);
} else {
throw Exception('Failed to load photos. Status: ${response.statusCode}');
}
}
// 3. Strongly typed model
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 Integration
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();
// Initialize Future once to prevent re-fetching on rebuilds
_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}'));
}
// Default loading state
return const Center(child: CircularProgressIndicator());
},
);
}
}
コメント
Markdown 対応