Contents
- Configuration and Permissions
- Request Execution and Response Handling
- Background Parsing
- Workflow: Executing Network Operations
- Examples
Configuration and Permissions
Configure the development environment and platform-specific access controls to enable network requests:
- Add the
httpdependency using your terminal:flutter pub add http - Import the library with an alias in your Dart files:
import 'package:http/http.dart' as http; - Enable internet permissions on Android by modifying
android/app/src/main/AndroidManifest.xmlwithin the<manifest>tag:<uses-permission android:name="android.permission.INTERNET" /> - Enable internet clients on macOS by modifying
macos/Runner/DebugProfile.entitlementsandmacos/Runner/Release.entitlementswithin the<dict>tag:<key>com.apple.security.network.client</key> <true/>
Request Execution and Response Handling
Design reliable REST clients by applying these best practices:
- Strict URL Parsing: Always parse endpoint strings via
Uri.parse('url'). Never pass raw strings to client calls. - Headers and Authentication: Attach all authorization, accept, and content-type configurations. Inject access tokens using the
HttpHeaders.authorizationHeaderkey fromdart:io. - Payload Encoding: When mutating resource states (POST, PUT), encode payload bodies with
jsonEncodefromdart:convert. - Status Validation: Verify response codes. Handle only explicit success status codes (e.g.
200 OKor201 Created). - Throw on Errors: Throw descriptive exceptions when the server responds with unsuccessful status codes. Never return
nullon failure, as this hides issues and results in infinite UI loading spinners. - Client Mocking: Accept an
http.Clientdependency in your network classes instead of calling standard global methods. This enables easy testing and mock injection.
Background Parsing
Offload JSON decoding and mapping to a background thread to prevent UI jank (dropped frames) when handling payloads larger than 1MB:
- Import
package:flutter/foundation.dart. - Run the parsing logic within the
compute()function to spawn a background isolate. - Ensure the parsing function is defined as a top-level function or static class method. Closures and standard instance methods cannot cross isolate boundaries.
Workflow: Executing Network Operations
Follow this checklist to build and verify network integration:
- Define model contracts: Create clear, immutable model classes with a custom
fromJsonfactory constructor. - Establish HTTP clients: Build the network client class accepting
http.Client. - Formulate requests:
- For reading (GET): Attach query parameters to the URI.
- For mutations (POST/PUT): Set
'Content-Type': 'application/json; charset=UTF-8'and attachjsonEncodedata. - For deletions (DELETE): Return success indicators or empty model mappings upon matching
200 OK.
- Enforce error handling: Throw meaningful exceptions for non-success status codes.
- Integrate UI state: Bind network requests to a
FutureBuilderor state management controller in the UI layer. - Verify boundaries: Test with proper loading screens, error dialogs, and offline-handling feedback loops.
Examples
Complete Network Client and Isolate Parser
This example demonstrates setting up a reliable, testable network client that parses complex payload lists in a background isolate.
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;
class Article {
final int id;
final String title;
const Article({required this.id, required this.title});
factory Article.fromJson(Map<String, dynamic> json) {
return Article(
id: json['id'] as int,
title: json['title'] as String,
);
}
}
// Top-level function for background isolate computation
List<Article> parseArticles(String responseBody) {
final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Article>((json) => Article.fromJson(json)).toList();
}
class ApiService {
final http.Client client;
ApiService({http.Client? client}) : client = client ?? http.Client();
Future<List<Article>> fetchArticles() async {
final response = await client.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts'),
headers: {
HttpHeaders.contentTypeHeader: 'application/json; charset=UTF-8',
HttpHeaders.acceptHeader: 'application/json',
},
);
if (response.statusCode == 200) {
// Offload to background isolate
return compute(parseArticles, response.body);
} else {
throw HttpException('Failed to load articles: ${response.statusCode}');
}
}
}