Dio
Powerful HTTP client library for Dart/Flutter. Provides interceptors, global configuration, FormData, request cancellation, file downloading, and timeout capabilities. Widely adopted in Flutter development due to rich features and excellent documentation.
GitHub Overview
cfug/dio
A powerful HTTP client for Dart and Flutter, which supports global settings, Interceptors, FormData, aborting and canceling a request, files uploading and downloading, requests timeout, custom adapters, etc.
Topics
Star History
Library
Dio
Overview
Dio is a powerful HTTP client library for Dart/Flutter that provides comprehensive HTTP communication capabilities including global configuration, interceptors, FormData, request cancellation, file upload/download, timeout settings, and custom adapters. With the concept of "HTTP for Humans," it delivers complex HTTP processing through simple and intuitive APIs. Widely adopted as the de facto standard HTTP client in Flutter development, it supports development across various platforms from mobile apps to web applications, making it an essential tool for modern application development.
Details
Dio 2025 edition maintains its solid position as the most trusted comprehensive HTTP client library in the Dart/Flutter ecosystem. Its rich interceptor functionality enables unified management of authentication, logging, and error handling, while FormData support provides complete file upload capabilities, progress tracking, and request cancellation features that cover all functions necessary for modern application development. With platform-specific adapters (IOHttpClientAdapter, BrowserHttpClientAdapter), it delivers optimal performance in both native and web environments, making it an indispensable library for enterprise-level application development.
Key Features
- Comprehensive Interceptors: Unified management of request, response, and error handling
- Complete FormData Support: Multipart form data and file upload capabilities
- Platform Optimization: Dedicated adapters for native/web environments
- Progress Tracking: Detailed progress monitoring for uploads/downloads
- Request Cancellation: Flexible request control via CancelToken
- Global Configuration: Unified configuration management through BaseOptions
Pros and Cons
Pros
- Overwhelming adoption rate in Dart/Flutter ecosystem with abundant learning resources
- Unified management of authentication, logging, and error handling through interceptor functionality
- Complete support for FormData and file upload features providing practical utility
- Optimal performance through platform-specific adapters
- Excellent user experience through progress tracking and cancellation features
- Improved development efficiency and code consistency through global configuration
Cons
- May be overly complex for simple HTTP requests due to its feature-rich nature
- Limitations in reusing FormData and MultipartFile instances (single-use restriction)
- Increased learning cost due to abundant configuration options
- Debugging difficulties due to complex interceptor chains
- Impact on app size due to numerous dependencies
- Limitations in some features due to platform-specific constraints
Reference Pages
Code Examples
Installation and Basic Setup
dependencies:
dio: ^5.4.0
# Security-enhanced version (recommended)
dependencies:
dio: ^5.4.0
dio_certificate_pinning: ^6.0.0
# Additional plugins
dev_dependencies:
dio_compatibility_layer: ^3.0.1
dio_web_adapter: ^1.0.0
native_dio_adapter: ^1.2.0
import 'package:dio/dio.dart';
// Basic Dio instance creation
final dio = Dio();
// Dio instance with configuration
final dioWithOptions = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
));
void main() {
print('Dio HTTP client initialized successfully');
}
Basic Requests (GET/POST/PUT/DELETE)
import 'package:dio/dio.dart';
final dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
headers: {'Authorization': 'Bearer your-token'},
));
// Basic GET request
Future<void> basicGetRequest() async {
try {
final response = await dio.get('/users');
print('Status Code: ${response.statusCode}');
print('Response Data: ${response.data}');
print('Headers: ${response.headers}');
} catch (e) {
print('Error: $e');
}
}
// GET request with query parameters
Future<void> getWithQueryParams() async {
final response = await dio.get(
'/users',
queryParameters: {
'page': 1,
'limit': 10,
'sort': 'created_at',
'filter': 'active'
},
);
print('URL: ${response.requestOptions.uri}');
print('Data: ${response.data}');
}
// POST request (sending JSON)
Future<void> postJsonData() async {
final userData = {
'name': 'John Doe',
'email': '[email protected]',
'age': 30,
'department': 'Engineering'
};
try {
final response = await dio.post(
'/users',
data: userData,
options: Options(
headers: {'X-Request-ID': 'req-${DateTime.now().millisecondsSinceEpoch}'},
),
);
if (response.statusCode == 201) {
final createdUser = response.data;
print('User created successfully: ID=${createdUser['id']}');
print('Created at: ${createdUser['created_at']}');
}
} on DioException catch (e) {
print('Request error: ${e.message}');
if (e.response != null) {
print('Status: ${e.response!.statusCode}');
print('Error details: ${e.response!.data}');
}
}
}
// PUT request (data update)
Future<void> updateUserData() async {
final updatedData = {
'name': 'Jane Doe',
'email': '[email protected]',
'department': 'Product Management'
};
final response = await dio.put(
'/users/123',
data: updatedData,
);
print('Update result: ${response.data}');
}
// DELETE request
Future<void> deleteUser() async {
try {
final response = await dio.delete('/users/123');
if (response.statusCode == 204) {
print('User deleted successfully');
} else if (response.statusCode == 200) {
print('Delete completed: ${response.data}');
}
} catch (e) {
print('Delete error: $e');
}
}
// Parallel execution of multiple requests
Future<void> parallelRequests() async {
final results = await Future.wait([
dio.get('/users'),
dio.get('/posts'),
dio.get('/comments'),
]);
print('Users count: ${results[0].data.length}');
print('Posts count: ${results[1].data.length}');
print('Comments count: ${results[2].data.length}');
}
Advanced Configuration and Customization (Interceptors, Authentication, Timeout, etc.)
import 'package:dio/dio.dart';
// Comprehensive Dio configuration and interceptors
class ApiClient {
late Dio _dio;
ApiClient() {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com/v1',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 10),
sendTimeout: Duration(seconds: 10),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'User-Agent': 'MyFlutterApp/1.0.0',
},
));
_setupInterceptors();
}
void _setupInterceptors() {
// Request interceptor
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
print('🚀 REQUEST[${options.method}] => PATH: ${options.path}');
print('📝 Headers: ${options.headers}');
print('📊 Data: ${options.data}');
// Automatic authentication token addition
final token = getAuthToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
// Request ID addition
options.headers['X-Request-ID'] = generateRequestId();
handler.next(options);
},
onResponse: (response, handler) {
print('✅ RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}');
print('⏱️ Duration: ${DateTime.now().difference(response.requestOptions.extra['start_time'] ?? DateTime.now())}');
handler.next(response);
},
onError: (error, handler) {
print('❌ ERROR[${error.response?.statusCode}] => PATH: ${error.requestOptions.path}');
print('💥 Message: ${error.message}');
// Automatic token refresh on 401 error
if (error.response?.statusCode == 401) {
_handleUnauthorizedError(error, handler);
} else {
handler.next(error);
}
},
),
);
// Log interceptor (for debugging)
_dio.interceptors.add(
LogInterceptor(
requestBody: true,
responseBody: true,
logPrint: (log) => print('📋 $log'),
),
);
}
// Authentication error handling
Future<void> _handleUnauthorizedError(
DioException error,
ErrorInterceptorHandler handler,
) async {
try {
final refreshed = await refreshAuthToken();
if (refreshed) {
// Re-execute original request when token refresh succeeds
final options = error.requestOptions;
options.headers['Authorization'] = 'Bearer ${getAuthToken()}';
final response = await _dio.fetch(options);
handler.resolve(response);
} else {
handler.next(error);
}
} catch (e) {
handler.next(error);
}
}
// Custom timeout configuration
Future<Response> customTimeoutRequest(String path, {
Duration? customTimeout,
Map<String, dynamic>? data,
}) async {
return await _dio.get(
path,
data: data,
options: Options(
sendTimeout: customTimeout ?? Duration(seconds: 30),
receiveTimeout: customTimeout ?? Duration(seconds: 30),
),
);
}
// HTTPS client certificate configuration
void setupClientCertificate() {
(_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
client.badCertificateCallback = (cert, host, port) {
// Implement proper certificate validation in production
return true; // Development environment only
};
return client;
};
}
String? getAuthToken() => 'your-auth-token';
String generateRequestId() => 'req-${DateTime.now().millisecondsSinceEpoch}';
Future<bool> refreshAuthToken() async => true; // Implementation omitted
}
// Proxy configuration
void setupProxy() {
final dio = Dio();
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
client.findProxy = (uri) {
return 'PROXY localhost:8888';
};
return client;
};
}
Error Handling and Retry Functionality
import 'package:dio/dio.dart';
// Comprehensive error handling
class ErrorHandler {
static Future<Response?> safeRequest(
Future<Response> Function() request, {
int maxRetries = 3,
Duration retryDelay = const Duration(seconds: 1),
}) async {
int attempts = 0;
while (attempts < maxRetries) {
try {
return await request();
} on DioException catch (e) {
attempts++;
print('Attempt $attempts/$maxRetries failed');
// Check if error is retryable
if (_shouldRetry(e) && attempts < maxRetries) {
print('Retrying in ${retryDelay.inSeconds} seconds...');
await Future.delayed(retryDelay);
continue;
}
// Analyze error details
_analyzeError(e);
// When max attempts reached or non-retryable error
if (attempts >= maxRetries) {
print('Maximum retry attempts reached');
}
rethrow;
} catch (e) {
print('Unexpected error: $e');
rethrow;
}
}
return null;
}
static bool _shouldRetry(DioException error) {
// Retryable conditions
if (error.type == DioExceptionType.connectionTimeout ||
error.type == DioExceptionType.receiveTimeout ||
error.type == DioExceptionType.sendTimeout) {
return true;
}
// Retry on 5xx server errors
if (error.response?.statusCode != null) {
final statusCode = error.response!.statusCode!;
return statusCode >= 500 && statusCode < 600;
}
return false;
}
static void _analyzeError(DioException error) {
print('=== Error Analysis ===');
print('Type: ${error.type}');
print('Message: ${error.message}');
if (error.response != null) {
final response = error.response!;
print('Status Code: ${response.statusCode}');
print('Status Message: ${response.statusMessage}');
print('Response Headers: ${response.headers}');
print('Response Data: ${response.data}');
} else {
print('No response (network error or timeout)');
}
// Detailed messages by error type
switch (error.type) {
case DioExceptionType.connectionTimeout:
print('💡 Connection timeout: Server connection taking too long');
break;
case DioExceptionType.receiveTimeout:
print('💡 Receive timeout: Data reception taking too long');
break;
case DioExceptionType.sendTimeout:
print('💡 Send timeout: Data transmission taking too long');
break;
case DioExceptionType.badResponse:
print('💡 Bad response: Server returned unexpected response');
break;
case DioExceptionType.cancel:
print('💡 Cancelled: Request was cancelled');
break;
case DioExceptionType.connectionError:
print('💡 Connection error: Please check network connection');
break;
case DioExceptionType.unknown:
print('💡 Unknown error: ${error.error}');
break;
}
}
}
// Usage example
Future<void> errorHandlingExample() async {
final dio = Dio();
// Safe request execution
final response = await ErrorHandler.safeRequest(
() => dio.get('https://api.example.com/unstable-endpoint'),
maxRetries: 5,
retryDelay: Duration(seconds: 2),
);
if (response != null) {
print('Request successful: ${response.data}');
} else {
print('Request failed');
}
}
// Request cancellation using CancelToken
Future<void> cancellationExample() async {
final dio = Dio();
final cancelToken = CancelToken();
// Cancel after 5 seconds
Timer(Duration(seconds: 5), () {
cancelToken.cancel('Cancelled by user');
});
try {
final response = await dio.get(
'https://api.example.com/slow-endpoint',
cancelToken: cancelToken,
);
print('Response: ${response.data}');
} on DioException catch (e) {
if (CancelToken.isCancel(e)) {
print('Request was cancelled: ${e.message}');
} else {
print('Other error: ${e.message}');
}
}
}
FormData and File Upload Functionality
import 'package:dio/dio.dart';
import 'dart:io';
// Comprehensive FormData and file upload examples
class FileUploadService {
final Dio _dio = Dio();
// Basic FormData submission
Future<void> basicFormDataUpload() async {
final formData = FormData.fromMap({
'user_name': 'John Doe',
'email': '[email protected]',
'age': '30',
'department': 'Engineering',
});
try {
final response = await _dio.post(
'https://api.example.com/users',
data: formData,
);
print('Form submission successful: ${response.data}');
} catch (e) {
print('Form submission error: $e');
}
}
// FormData upload with file
Future<void> fileUploadWithFormData() async {
try {
final formData = FormData.fromMap({
'title': 'Profile Image',
'description': 'User profile photo',
'category': 'profile',
'file': await MultipartFile.fromFile(
'/path/to/profile.jpg',
filename: 'profile.jpg',
contentType: DioMediaType.parse('image/jpeg'),
),
});
final response = await _dio.post(
'https://api.example.com/upload',
data: formData,
onSendProgress: (sent, total) {
final progress = (sent / total * 100).toStringAsFixed(1);
print('Upload progress: $progress% ($sent/$total bytes)');
},
);
print('Upload successful: ${response.data}');
} catch (e) {
print('Upload error: $e');
}
}
// Multiple file upload
Future<void> multipleFileUpload() async {
try {
final formData = FormData.fromMap({
'album_name': 'Travel Photos',
'description': 'Photos taken during summer 2024 trip',
'files': [
await MultipartFile.fromFile(
'/path/to/photo1.jpg',
filename: 'photo1.jpg',
contentType: DioMediaType.parse('image/jpeg'),
),
await MultipartFile.fromFile(
'/path/to/photo2.jpg',
filename: 'photo2.jpg',
contentType: DioMediaType.parse('image/jpeg'),
),
await MultipartFile.fromFile(
'/path/to/photo3.jpg',
filename: 'photo3.jpg',
contentType: DioMediaType.parse('image/jpeg'),
),
],
});
final response = await _dio.post(
'https://api.example.com/upload-multiple',
data: formData,
onSendProgress: (sent, total) {
final progress = (sent / total * 100).toStringAsFixed(1);
print('Multiple file upload progress: $progress%');
},
);
print('Multiple file upload successful: ${response.data}');
} catch (e) {
print('Multiple file upload error: $e');
}
}
// File upload from bytes
Future<void> bytesUpload() async {
final imageBytes = await File('/path/to/image.png').readAsBytes();
final formData = FormData.fromMap({
'title': 'Bytes Data Image',
'file': MultipartFile.fromBytes(
imageBytes,
filename: 'bytes_image.png',
contentType: DioMediaType.parse('image/png'),
),
});
final response = await _dio.post(
'https://api.example.com/upload-bytes',
data: formData,
);
print('Bytes upload successful: ${response.data}');
}
// Large file upload using stream
Future<void> streamUpload() async {
final file = File('/path/to/large-video.mp4');
final fileSize = await file.length();
final formData = FormData.fromMap({
'title': 'Large Video File',
'file': MultipartFile.fromStream(
() => file.openRead(),
fileSize,
filename: 'large-video.mp4',
contentType: DioMediaType.parse('video/mp4'),
),
});
try {
final response = await _dio.post(
'https://api.example.com/upload-stream',
data: formData,
options: Options(
sendTimeout: Duration(minutes: 10), // For large files
),
onSendProgress: (sent, total) {
final progress = (sent / total * 100).toStringAsFixed(2);
final sentMB = (sent / 1024 / 1024).toStringAsFixed(2);
final totalMB = (total / 1024 / 1024).toStringAsFixed(2);
print('Large file upload: $progress% (${sentMB}MB/${totalMB}MB)');
},
);
print('Large file upload successful: ${response.data}');
} catch (e) {
print('Large file upload error: $e');
}
}
// Reusable FormData creation function
Future<FormData> createReusableFormData() async {
return FormData.fromMap({
'timestamp': DateTime.now().toIso8601String(),
'app_version': '1.0.0',
'platform': Platform.isAndroid ? 'android' : 'ios',
});
}
// FormData reuse example (Note: clone required)
Future<void> reuseFormDataExample() async {
final baseFormData = await createReusableFormData();
// First request
try {
final response1 = await _dio.post(
'https://api.example.com/endpoint1',
data: baseFormData.clone(), // Use clone
);
print('First request successful: ${response1.data}');
} catch (e) {
print('First request error: $e');
}
// Second request
try {
final response2 = await _dio.post(
'https://api.example.com/endpoint2',
data: baseFormData.clone(), // Clone again for reuse
);
print('Second request successful: ${response2.data}');
} catch (e) {
print('Second request error: $e');
}
}
}
File Download and Progress Tracking
import 'package:dio/dio.dart';
import 'dart:io';
// File download service
class FileDownloadService {
final Dio _dio = Dio();
// Basic file download
Future<void> basicDownload() async {
try {
final response = await _dio.download(
'https://api.example.com/files/document.pdf',
'/downloads/document.pdf',
onReceiveProgress: (received, total) {
if (total != -1) {
final progress = (received / total * 100).toStringAsFixed(1);
print('Download progress: $progress% ($received/$total bytes)');
} else {
print('Downloading: ${received} bytes');
}
},
);
print('Download completed: ${response.statusCode}');
} catch (e) {
print('Download error: $e');
}
}
// Large file streaming download
Future<void> streamingDownload() async {
try {
final response = await _dio.get<ResponseBody>(
'https://api.example.com/files/large-dataset.zip',
options: Options(responseType: ResponseType.stream),
);
final file = File('/downloads/large-dataset.zip');
final sink = file.openWrite();
int downloaded = 0;
final contentLength = int.tryParse(
response.headers.value('content-length') ?? '0'
) ?? 0;
await for (final chunk in response.data!.stream) {
sink.add(chunk);
downloaded += chunk.length;
if (contentLength > 0) {
final progress = (downloaded / contentLength * 100).toStringAsFixed(2);
final downloadedMB = (downloaded / 1024 / 1024).toStringAsFixed(2);
final totalMB = (contentLength / 1024 / 1024).toStringAsFixed(2);
print('Streaming download: $progress% (${downloadedMB}MB/${totalMB}MB)');
}
}
await sink.close();
print('Streaming download completed');
} catch (e) {
print('Streaming download error: $e');
}
}
// Resumable download
Future<void> resumableDownload() async {
final filePath = '/downloads/resumable-file.zip';
final file = File(filePath);
int startByte = 0;
if (await file.exists()) {
startByte = await file.length();
print('Existing file detected: resuming from ${startByte} bytes');
}
try {
final response = await _dio.get<ResponseBody>(
'https://api.example.com/files/large-file.zip',
options: Options(
responseType: ResponseType.stream,
headers: {
'Range': 'bytes=$startByte-',
},
),
);
final sink = file.openWrite(mode: FileMode.append);
int downloaded = startByte;
// Get total size from Content-Range header
final contentRange = response.headers.value('content-range');
final totalSize = contentRange != null
? int.tryParse(contentRange.split('/').last) ?? 0
: 0;
await for (final chunk in response.data!.stream) {
sink.add(chunk);
downloaded += chunk.length;
if (totalSize > 0) {
final progress = (downloaded / totalSize * 100).toStringAsFixed(2);
print('Resume download: $progress% ($downloaded/$totalSize bytes)');
}
}
await sink.close();
print('Resume download completed');
} catch (e) {
print('Resume download error: $e');
}
}
// Parallel downloads of multiple files
Future<void> parallelDownloads() async {
final downloadTasks = [
'https://api.example.com/files/file1.pdf',
'https://api.example.com/files/file2.jpg',
'https://api.example.com/files/file3.mp4',
];
final futures = downloadTasks.asMap().entries.map((entry) {
final index = entry.key;
final url = entry.value;
final filename = url.split('/').last;
return _dio.download(
url,
'/downloads/$filename',
onReceiveProgress: (received, total) {
if (total != -1) {
final progress = (received / total * 100).toStringAsFixed(1);
print('File ${index + 1} download: $progress%');
}
},
);
});
try {
await Future.wait(futures);
print('All files download completed');
} catch (e) {
print('Parallel download error: $e');
}
}
}