| 1 | --- |
| 2 | name: flutter-use-http-package |
| 3 | description: Use the `http` package to execute GET, POST, PUT, or DELETE requests. Use when you need to fetch from or send data to a REST API. |
| 4 | metadata: |
| 5 | model: models/gemini-3.1-pro-preview |
| 6 | last_modified: Tue, 21 Apr 2026 21:36:42 GMT |
| 7 | --- |
| 8 | # Implementing Flutter Networking |
| 9 | |
| 10 | ## Contents |
| 11 | - [Configuration & Permissions](#configuration--permissions) |
| 12 | - [Request Execution & Response Handling](#request-execution--response-handling) |
| 13 | - [Background Parsing](#background-parsing) |
| 14 | - [Workflow: Executing Network Operations](#workflow-executing-network-operations) |
| 15 | - [Examples](#examples) |
| 16 | |
| 17 | ## Configuration & Permissions |
| 18 | |
| 19 | Configure the environment and platform-specific permissions required for network access. |
| 20 | |
| 21 | 1. Add the `http` package dependency via the terminal: |
| 22 | ```bash |
| 23 | flutter pub add http |
| 24 | ``` |
| 25 | 2. Import the package in your Dart files: |
| 26 | ```dart |
| 27 | import 'package:http/http.dart' as http; |
| 28 | ``` |
| 29 | 3. Configure Android permissions by adding the Internet permission to `android/app/src/main/AndroidManifest.xml`: |
| 30 | ```xml |
| 31 | <uses-permission android:name="android.permission.INTERNET" /> |
| 32 | ``` |
| 33 | 4. Configure macOS entitlements by adding the network client key to both `macos/Runner/DebugProfile.entitlements` and `macos/Runner/Release.entitlements`: |
| 34 | ```xml |
| 35 | <key>com.apple.security.network.client</key> |
| 36 | <true/> |
| 37 | ``` |
| 38 | |
| 39 | ## Request Execution & Response Handling |
| 40 | |
| 41 | Execute HTTP operations and map responses to strongly typed Dart objects. |
| 42 | |
| 43 | * **URIs:** Always parse URL strings using `Uri.parse('your_url')`. |
| 44 | * **Headers:** Inject authorization and content-type headers via the `headers` parameter map. Use `HttpHeaders.authorizationHeader` for auth tokens. |
| 45 | * **Payloads:** For POST and PUT requests, encode the body using `jsonEncode()` from `dart:convert`. |
| 46 | * **Status Validation:** Evaluate `response.statusCode`. Treat `200 OK` (GET/PUT/DELETE) and `201 CREATED` (POST) as success. |
| 47 | * **Error Handling:** Throw explicit exceptions for non-success status codes. Never return `null` on failure, as this prevents `FutureBuilder` from triggering its error state and causes infinite loading indicators. |
| 48 | * **Deserialization:** Parse the raw string using `jsonDecode(response.body)` and map it to a custom Dart object using a factory constructor (e.g., `fromJson`). |
| 49 | |
| 50 | ## Background Parsing |
| 51 | |
| 52 | Offload expensive JSON parsing to a separate Isolate to prevent UI jank (frame drops). |
| 53 | |
| 54 | * Import `package:flutter/foundation.dart`. |
| 55 | * Use the `compute()` function to run the parsing logic in a background isolate. |
| 56 | * Ensure the parsing function passed to `compute()` is a top-level function or a static method, as closures or instance methods cannot be passed across isolates. |
| 57 | |
| 58 | ## Workflow: Executing Network Operations |
| 59 | |
| 60 | Use the following checklist to implement and validate network operations. |
| 61 | |
| 62 | **Task Progress:** |
| 63 | - [ ] 1. Define the strongly typed Dart model with a `fromJson` factory constructor. |
| 64 | - [ ] 2. Implement the network request method returning a `Future<Model>`. |
| 65 | - [ ] 3. Apply conditional logic based on the operation type: |
| 66 | - **If fetching data (GET):** Append query parameters to the URI. |
| 67 | - **If mutating data (POST/PUT):** Set `'Content-Type': 'application/json; charset=UTF-8'` and attach the `jsonEncode` body. |
| 68 | - **If deleting data (DELETE):** Return an empty model instance on success (`200 OK`). |
| 69 | - [ ] 4. Validate the `statusCode` and throw an `Exception` on failure. |
| 70 | - [ ] 5. Integrate the `Future` into the UI using `FutureBuilder`. |
| 71 | - [ ] 6. Handle `snapshot.hasData`, `snapshot.hasError`, and default to a `CircularProgressIndicator`. |
| 72 | - [ ] 7. **Feedback Loop:** Run the app -> trigger the network request -> review console for unhandled exceptions -> fix parsing or permission errors. |
| 73 | |
| 74 | ## Examples |
| 75 | |
| 76 | ### High-Fidelity Implementation: Fetching and Parsing in the Background |
| 77 | |
| 78 | ```dart |
| 79 | import 'dart:async'; |
| 80 | import 'dart:convert'; |
| 81 | import 'dart:io'; |
| 82 | import 'package:flutter/foundation.dart'; |
| 83 | import 'package:flutter/material.dart'; |
| 84 | import 'package:http/http.dart' as http; |
| 85 | |
| 86 | // 1. Top-level parsing function for Isolate |
| 87 | List<Photo> parsePhotos(String responseBody) { |
| 88 | final parsed = (jsonDecode(responseBody) as List<Object?>) |
| 89 | .cast<Map<String, Object?>>(); |
| 90 | return parsed.map<Photo>(Photo.fromJson).toList(); |
| 91 | } |
| 92 | |
| 93 | // 2. Network execution with background parsing |
| 94 | Future<List<Photo>> fetchPhotos() async { |
| 95 | final response = await http.get( |
| 96 | Uri.parse('https://jsonplaceholder.typicode.com/photos'), |
| 97 | headers: { |
| 98 | HttpHeaders.authorizationHeader: 'Bearer your_token_here', |
| 99 | HttpHeaders.acceptHeader: 'application/json', |
| 100 | }, |
| 101 | ); |
| 102 | |
| 103 | if (response.statusCode == 200) { |
| 104 | // Offload heavy parsing to a background isolate |
| 105 | return compute(parsePhotos, response.body); |
| 106 | } else { |
| 107 | throw Exception('Failed to load photos. Status: ${response.statusCode}'); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | // 3. Strongly typed model |
| 112 | class Photo { |
| 113 | final int id; |
| 114 | final String title; |
| 115 | final String thumbnailUrl; |
| 116 | |
| 117 | const Photo({ |
| 118 | required this.id, |
| 119 | required this.title, |
| 120 | required this.thumbnailUrl, |
| 121 | }); |
| 122 | |
| 123 | factory Photo.fromJson(Map<String, dynamic> json) { |
| 124 | return Photo( |
| 125 | id: json['id'] as int, |
| 126 | title: json['title'] as String, |
| 127 | thumbnailUrl: json['thumbnailUrl'] as String, |
| 128 | ); |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | // 4. UI Integration |
| 133 | class PhotoGallery extends StatefulWidget { |
| 134 | const PhotoGallery({super.key}); |
| 135 | |
| 136 | @override |
| 137 | State<PhotoGallery> createState() => _PhotoGalleryState(); |
| 138 | } |
| 139 | |
| 140 | class _PhotoGalleryState extends State<PhotoGallery> { |
| 141 | late Future<List<Photo>> _futurePhotos; |
| 142 | |
| 143 | @override |
| 144 | void initState() { |
| 145 | super.initState(); |
| 146 | // Initialize Future once to prevent re-fetching on rebuilds |
| 147 | _futurePhotos = fetchPhotos(); |
| 148 | } |
| 149 | |
| 150 | @override |
| 151 | Widget build(BuildContext context) { |
| 152 | return FutureBuilder<List<Photo>>( |
| 153 | future: _futurePhotos, |
| 154 | builder: (context, snapshot) { |
| 155 | if (snapshot.hasData) { |
| 156 | final photos = snapshot.data!; |
| 157 | return ListView.builder( |
| 158 | itemCount: photos.length, |
| 159 | itemBuilder: (context, index) => ListTile( |
| 160 | leading: Image.network(photos[index].thumbnailUrl), |
| 161 | title: Text(photos[index].title), |
| 162 | ), |
| 163 | ); |
| 164 | } else if (snapshot.hasError) { |
| 165 | return Center(child: Text('Error: ${snapshot.error}')); |
| 166 | } |
| 167 | |
| 168 | // Default loading state |
| 169 | return const Center(child: CircularProgressIndicator()); |
| 170 | }, |
| 171 | ); |
| 172 | } |
| 173 | } |
| 174 | ``` |