Networking with Dio
- Use Dio as the primary HTTP client package.
- Use type-safe model classes with
fromJson / toJson factories for all request/response bodies.
- Handle all HTTP status codes appropriately with typed exceptions (e.g.,
ServerException, NetworkException, UnauthorizedException).
- Use proper request timeouts (
connectTimeout, receiveTimeout, sendTimeout).
Dio Interceptors
- Use interceptors for cross-cutting concerns:
- Auth Interceptor: Attach access tokens to headers, handle token refresh on 401.
- Logging Interceptor: Log requests/responses in debug mode via
AppLogger.
- Error Interceptor: Transform
DioException into domain-specific Failure types.
- Register interceptors centrally via
injectable for consistent behavior across all API calls.
Repository Pattern
- DataSources contain only raw Dio API calls: no business logic or mapping
- Repositories orchestrate between remote DataSources and local cache for network data
Retry & Resilience
- Implement retry logic with exponential backoff for transient failures (e.g., 500, timeout).
- Set a maximum retry count (default: 3 retries).
- Cache responses when appropriate to reduce network calls and improve offline UX.
Performance
- Parse JSON in background isolates for large responses (> 1MB) using
compute()
- Do NOT block the UI thread with synchronous network operations
Security
- Store tokens via
flutter_secure_storage: never in source code or SharedPreferences
- All API communication MUST use HTTPS
Alternative: http Package
For simple REST calls that don't need interceptors, caching, or retry logic, use http instead of Dio.
Dio vs http
| Criteria |
http |
Dio |
| Interceptors |
No |
Yes, full chain |
| Retry logic |
Manual |
Built-in with backoff |
| Response caching |
Manual |
Plugin available |
| FormData / Multipart |
Manual |
Built-in |
| Cancel requests |
No |
Yes, CancelToken |
| Dependencies |
Minimal (1 package) |
Heavier |
| Use case |
Simple CRUD APIs |
Production API clients |
Use http for prototypes and simple fetch-and-display. Use Dio for production API clients that need auth, retry, and caching.
Basic http Patterns
import 'dart:convert';
import 'package:http/http.dart' as http;
// GET request
Future<Map<String, dynamic>> fetchData(http.Client client) async {
final response = await client.get(
Uri.parse('https://api.example.com/data'),
headers: {'Accept': 'application/json'},
);
if (response.statusCode == 200) {
return jsonDecode(response.body) as Map<String, dynamic>;
} else {
throw Exception('Failed to fetch: ${response.statusCode}');
}
}
// POST request
Future<void> createItem(http.Client client, Map<String, dynamic> body) async {
final response = await client.post(
Uri.parse('https://api.example.com/items'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(body),
);
if (response.statusCode != 201) {
throw Exception('Failed to create: ${response.statusCode}');
}
}
Testing: Always accept http.Client as a parameter (not http.get() directly) to enable mock injection in tests.
1---2name: flutter-dio3description: Use when configuring HTTP network clients with Dio, adding auth token interceptors, handling retry logic, or caching API responses.4---5
6# Networking with Dio
7
8- Use **Dio** as the primary HTTP client package.
9- Use type-safe model classes with `fromJson` / `toJson` factories for all request/response bodies.
10- Handle all HTTP status codes appropriately with typed exceptions (e.g., `ServerException`, `NetworkException`, `UnauthorizedException`).
11- Use proper request timeouts (`connectTimeout`, `receiveTimeout`, `sendTimeout`).
12
13# Dio Interceptors
14
15- Use interceptors for cross-cutting concerns:
16 - **Auth Interceptor**: Attach access tokens to headers, handle token refresh on 401.
17 - **Logging Interceptor**: Log requests/responses in debug mode via `AppLogger`.
18 - **Error Interceptor**: Transform `DioException` into domain-specific `Failure` types.
19- Register interceptors centrally via `injectable` for consistent behavior across all API calls.
20
21# Repository Pattern
22
23- DataSources contain only raw Dio API calls: no business logic or mapping
24- Repositories orchestrate between remote DataSources and local cache for network data
25
26# Retry & Resilience
27
28- Implement retry logic with exponential backoff for transient failures (e.g., 500, timeout).
29- Set a maximum retry count (default: 3 retries).
30- Cache responses when appropriate to reduce network calls and improve offline UX.
31
32# Performance
33
34- Parse JSON in background isolates for large responses (> 1MB) using `compute()`
35- Do NOT block the UI thread with synchronous network operations
36
37# Security
38
39- Store tokens via `flutter_secure_storage`: never in source code or `SharedPreferences`
40- All API communication MUST use HTTPS
41
42# Alternative: http Package
43
44For simple REST calls that don't need interceptors, caching, or retry logic, use `http` instead of Dio.
45
46## Dio vs http
47
48| Criteria | `http` | `Dio` |
49|---|---|---|
50| **Interceptors** | No | Yes, full chain |
51| **Retry logic** | Manual | Built-in with backoff |
52| **Response caching** | Manual | Plugin available |
53| **FormData / Multipart** | Manual | Built-in |
54| **Cancel requests** | No | Yes, `CancelToken` |
55| **Dependencies** | Minimal (1 package) | Heavier |
56| **Use case** | Simple CRUD APIs | Production API clients |
57
58Use `http` for prototypes and simple fetch-and-display. Use `Dio` for production API clients that need auth, retry, and caching.
59
60## Basic http Patterns
61
62```dart
63import 'dart:convert';
64import 'package:http/http.dart' as http;
65
66// GET request
67Future<Map<String, dynamic>> fetchData(http.Client client) async {
68 final response = await client.get(
69 Uri.parse('https://api.example.com/data'),
70 headers: {'Accept': 'application/json'},
71 );
72
73 if (response.statusCode == 200) {
74 return jsonDecode(response.body) as Map<String, dynamic>;
75 } else {
76 throw Exception('Failed to fetch: ${response.statusCode}');
77 }
78}
79
80// POST request
81Future<void> createItem(http.Client client, Map<String, dynamic> body) async {
82 final response = await client.post(
83 Uri.parse('https://api.example.com/items'),
84 headers: {'Content-Type': 'application/json'},
85 body: jsonEncode(body),
86 );
87
88 if (response.statusCode != 201) {
89 throw Exception('Failed to create: ${response.statusCode}');
90 }
91}
92```
93
94**Testing**: Always accept `http.Client` as a parameter (not `http.get()` directly) to enable mock injection in tests.