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