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.

HTTP ClientDartFlutterInterceptorsFormDataFile Upload

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.

Stars12,731
Watchers163
Forks1,547
Created:April 20, 2018
Language:Dart
License:MIT License

Topics

adaptercancellabledartdioflutterhttpinterceptormiddlewarenetworktimeouttransformer

Star History

cfug/dio Star History
Data as of: 10/22/2025, 09:55 AM

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');
    }
  }
}