Fetching Data with Dio Package
Dio is a powerful HTTP client for Dart/Flutter that supports Interceptors, Global configuration, FormData, Request cancellation, File downloading, Timeout, etc. This guide will show you how to effectively use Dio for making API requests in your Flutter applications.
Why Use Dio?
Dio offers several advantages over the default http package:
- Interceptors for request/response handling
- Global configuration
- FormData support
- Request cancellation
- File downloading
- Timeout handling
- Better error handling
- Type-safe responses
Implementation Steps
-
Setup Dependencies
# pubspec.yaml dependencies: dio: ^5.4.0 json_annotation: ^4.8.1 dev_dependencies: build_runner: ^2.4.8 json_serializable: ^6.7.1
-
Create API Client
class ApiClient { late Dio _dio; ApiClient() { _dio = Dio(BaseOptions( baseUrl: 'https://api.example.com', connectTimeout: Duration(seconds: 5), receiveTimeout: Duration(seconds: 3), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, )); _dio.interceptors.add(LogInterceptor( request: true, requestHeader: true, requestBody: true, responseHeader: true, responseBody: true, error: true, )); } Future<Response> get(String path, {Map<String, dynamic>? queryParameters}) async { try { final response = await _dio.get(path, queryParameters: queryParameters); return response; } on DioException catch (e) { throw _handleError(e); } } Future<Response> post(String path, {dynamic data}) async { try { final response = await _dio.post(path, data: data); return response; } on DioException catch (e) { throw _handleError(e); } } Exception _handleError(DioException error) { switch (error.type) { case DioExceptionType.connectionTimeout: case DioExceptionType.sendTimeout: case DioExceptionType.receiveTimeout: return TimeoutException('Connection timeout'); case DioExceptionType.badResponse: return ApiException( error.response?.statusCode, error.response?.data['message'] ?? 'Unknown error', ); case DioExceptionType.cancel: return RequestCancelledException(); default: return NetworkException(); } } }
-
Create Model Classes
@JsonSerializable() class User { final int id; final String name; final String email; User({ required this.id, required this.name, required this.email, }); factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json); Map<String, dynamic> toJson() => _$UserToJson(this); }
-
Implement Repository Pattern
class UserRepository { final ApiClient _apiClient; UserRepository(this._apiClient); Future<List<User>> getUsers() async { final response = await _apiClient.get('/users'); return (response.data as List) .map((json) => User.fromJson(json)) .toList(); } Future<User> getUser(int id) async { final response = await _apiClient.get('/users/$id'); return User.fromJson(response.data); } Future<User> createUser(User user) async { final response = await _apiClient.post('/users', data: user.toJson()); return User.fromJson(response.data); } }
-
Create Service Layer
class UserService { final UserRepository _repository; UserService(this._repository); Future<List<User>> fetchUsers() async { try { return await _repository.getUsers(); } catch (e) { // Handle error appropriately rethrow; } } Future<User> fetchUser(int id) async { try { return await _repository.getUser(id); } catch (e) { // Handle error appropriately rethrow; } } Future<User> createUser(User user) async { try { return await _repository.createUser(user); } catch (e) { // Handle error appropriately rethrow; } } }
Advanced Features
-
Request Interceptors
class AuthInterceptor extends Interceptor { @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { // Add auth token options.headers['Authorization'] = 'Bearer $token'; super.onRequest(options, handler); } @override void onError(DioException err, ErrorInterceptorHandler handler) { if (err.response?.statusCode == 401) { // Handle unauthorized error } super.onError(err, handler); } }
-
File Upload
Future<void> uploadFile(String filePath) async { FormData formData = FormData.fromMap({ 'file': await MultipartFile.fromFile(filePath), }); await _dio.post('/upload', data: formData); }
-
Download Progress
Future<void> downloadFile(String url, String savePath) async { await _dio.download( url, savePath, onReceiveProgress: (received, total) { if (total != -1) { print((received / total * 100).toStringAsFixed(0) + '%'); } }, ); }
Best Practices
-
Error Handling
- Implement proper error classes
- Handle network errors gracefully
- Provide user-friendly error messages
- Implement retry mechanisms
-
Caching
- Implement response caching
- Handle cache invalidation
- Use appropriate cache policies
- Monitor cache size
-
Security
- Secure API endpoints
- Handle sensitive data
- Implement proper authentication
- Use HTTPS
Common Use Cases
-
REST API Integration
- CRUD operations
- Authentication
- File upload/download
- Pagination
-
Real-time Updates
- WebSocket integration
- Polling
- Push notifications
- Data synchronization
-
Data Management
- Caching strategies
- Offline support
- Data persistence
- State management
Conclusion
Dio is a powerful and flexible HTTP client that makes it easy to work with APIs in Flutter applications. By following these guidelines and implementing the provided examples, you can create robust and maintainable networking code that handles various API scenarios effectively.