Skip to main content

One post tagged with "Networking"

View All Tags

Giới Thiệu Thư Viện Dio và Lý Do Nên Dùng Thay HTTP trong Flutter

· 8 min read

Khi phát triển ứng dụng Flutter, việc gọi API là một phần không thể thiếu. Trong khi package http là lựa chọn cơ bản, Dio là một giải pháp mạnh mẽ và linh hoạt hơn nhiều. Bài viết này sẽ giới thiệu về Dio và giải thích tại sao bạn nên sử dụng nó thay vì http package.


1️⃣ Dio Là Gì?

Dio là một HTTP client mạnh mẽ cho Dart/Flutter, được phát triển bởi Flutter China. Dio cung cấp nhiều tính năng nâng cao mà package http cơ bản không có.

1.1 Cài Đặt Dio

Thêm Dio vào file pubspec.yaml:

dependencies:
dio: ^5.4.0

Sau đó chạy:

flutter pub get

1.2 Import Dio

import 'package:dio/dio.dart';

2️⃣ So Sánh Dio vs HTTP Package

2.1 HTTP Package - Giải Pháp Cơ Bản

import 'package:http/http.dart' as http;
import 'dart:convert';

// Gọi API với http package
Future<void> fetchData() async {
final response = await http.get(
Uri.parse('https://api.example.com/data'),
headers: {'Content-Type': 'application/json'},
);

if (response.statusCode == 200) {
final data = json.decode(response.body);
print(data);
} else {
print('Error: ${response.statusCode}');
}
}

Hạn chế của HTTP package:

  • ❌ Không có interceptors
  • ❌ Không hỗ trợ retry tự động
  • ❌ Không có request/response transformers
  • ❌ Xử lý lỗi thủ công
  • ❌ Không có timeout configuration dễ dàng
  • ❌ Không hỗ trợ cancel requests

2.2 Dio - Giải Pháp Mạnh Mẽ

import 'package:dio/dio.dart';

// Gọi API với Dio
Future<void> fetchData() async {
final dio = Dio();

try {
final response = await dio.get('https://api.example.com/data');
print(response.data);
} catch (e) {
print('Error: $e');
}
}

Ưu điểm của Dio:

  • ✅ Interceptors mạnh mẽ
  • ✅ Retry logic tự động
  • ✅ Request/Response transformers
  • ✅ Xử lý lỗi tốt hơn
  • ✅ Timeout configuration dễ dàng
  • ✅ Hỗ trợ cancel requests
  • ✅ Hỗ trợ file upload/download
  • ✅ Progress callbacks

3️⃣ Các Tính Năng Nổi Bật Của Dio

3.1 Interceptors

Dio cho phép bạn thêm interceptors để xử lý requests và responses trước/sau khi gửi/nhận:

final dio = Dio();

// Thêm interceptor để log requests
dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
));

// Thêm interceptor tùy chỉnh
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
// Thêm token vào header
options.headers['Authorization'] = 'Bearer your_token';
return handler.next(options);
},
onResponse: (response, handler) {
// Xử lý response
return handler.next(response);
},
onError: (error, handler) {
// Xử lý lỗi
return handler.next(error);
},
));

3.2 Retry Logic

Dio hỗ trợ retry tự động khi request thất bại:

import 'package:dio/retry.dart';

final dio = Dio();

dio.interceptors.add(
RetryInterceptor(
dio: dio,
options: const RetryOptions(
retries: 3,
retryInterval: Duration(seconds: 2),
),
),
);

3.3 Request/Response Transformers

Transform dữ liệu trước khi gửi/nhận:

final dio = Dio();

dio.transformer = BackgroundTransformer()..jsonDecodeCallback = parseJson;

// Custom JSON parser
Map<String, dynamic> parseJson(String text) {
// Custom parsing logic
return jsonDecode(text);
}

3.4 Timeout Configuration

Cấu hình timeout dễ dàng:

final dio = Dio(BaseOptions(
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
sendTimeout: Duration(seconds: 3),
));

3.5 Cancel Requests

Hủy request khi không cần thiết:

final cancelToken = CancelToken();

// Gọi API với cancel token
dio.get(
'https://api.example.com/data',
cancelToken: cancelToken,
);

// Hủy request
cancelToken.cancel('Request cancelled');

3.6 File Upload/Download

Dio hỗ trợ upload và download file với progress callback:

// Upload file
FormData formData = FormData.fromMap({
'file': await MultipartFile.fromFile('/path/to/file'),
});

await dio.post(
'https://api.example.com/upload',
data: formData,
onSendProgress: (sent, total) {
print('Progress: ${(sent / total * 100).toStringAsFixed(0)}%');
},
);

// Download file
await dio.download(
'https://api.example.com/file',
'/path/to/save/file',
onReceiveProgress: (received, total) {
print('Progress: ${(received / total * 100).toStringAsFixed(0)}%');
},
);

4️⃣ Lý Do Nên Dùng Dio Thay HTTP

4.1 Code Sạch Hơn và Dễ Bảo Trì

Với HTTP package:

Future<Map<String, dynamic>> fetchUser(int userId) async {
final uri = Uri.parse('https://api.example.com/users/$userId');
final response = await http.get(
uri,
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
);

if (response.statusCode == 200) {
return json.decode(response.body);
} else if (response.statusCode == 401) {
throw UnauthorizedException();
} else if (response.statusCode == 404) {
throw NotFoundException();
} else {
throw ServerException();
}
}

Với Dio:

Future<Map<String, dynamic>> fetchUser(int userId) async {
try {
final response = await dio.get('/users/$userId');
return response.data;
} on DioException catch (e) {
if (e.response?.statusCode == 401) {
throw UnauthorizedException();
} else if (e.response?.statusCode == 404) {
throw NotFoundException();
} else {
throw ServerException();
}
}
}

4.2 Xử Lý Lỗi Tốt Hơn

Dio cung cấp DioException với nhiều thông tin hữu ích:

try {
await dio.get('/api/data');
} on DioException catch (e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
print('Connection timeout');
break;
case DioExceptionType.sendTimeout:
print('Send timeout');
break;
case DioExceptionType.receiveTimeout:
print('Receive timeout');
break;
case DioExceptionType.badResponse:
print('Bad response: ${e.response?.statusCode}');
break;
case DioExceptionType.cancel:
print('Request cancelled');
break;
default:
print('Other error: ${e.message}');
}
}

4.3 Interceptors Cho Toàn Bộ App

Với Dio, bạn có thể cấu hình interceptors một lần và áp dụng cho tất cả requests:

class ApiClient {
static final Dio _dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
headers: {'Content-Type': 'application/json'},
));

static void init() {
// Thêm token vào mọi request
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
final token = TokenManager.getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
return handler.next(options);
},
));

// Log requests
_dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
));

// Retry logic
_dio.interceptors.add(
RetryInterceptor(
dio: _dio,
options: const RetryOptions(retries: 3),
),
);
}

static Dio get dio => _dio;
}

4.4 Type Safety Tốt Hơn

Dio hỗ trợ generic types cho responses:

// Định nghĩa model
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) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
}

// Sử dụng với Dio
Future<User> fetchUser(int id) async {
final response = await dio.get<Map<String, dynamic>>('/users/$id');
return User.fromJson(response.data!);
}

4.5 Hỗ Trợ Nhiều Format Dữ Liệu

Dio tự động xử lý JSON, FormData, và các format khác:

// JSON (mặc định)
await dio.post('/api/data', data: {'key': 'value'});

// FormData
await dio.post('/api/upload', data: FormData.fromMap({
'file': multipartFile,
'name': 'filename',
}));

// URL encoded
await dio.post(
'/api/data',
data: {'key': 'value'},
options: Options(contentType: Headers.formUrlEncodedContentType),
);

5️⃣ Ví Dụ Thực Tế: Tạo API Client Với Dio

import 'package:dio/dio.dart';

class ApiService {
late Dio _dio;

ApiService() {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
headers: {
'Content-Type': 'application/json',
},
));

_setupInterceptors();
}

void _setupInterceptors() {
// Log interceptor
_dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
error: true,
));

// Auth interceptor
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
final token = _getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
return handler.next(options);
},
onError: (error, handler) {
if (error.response?.statusCode == 401) {
// Refresh token hoặc đăng nhập lại
_handleUnauthorized();
}
return handler.next(error);
},
));
}

String? _getToken() {
// Lấy token từ storage
return 'your_token_here';
}

void _handleUnauthorized() {
// Xử lý khi unauthorized
}

// GET request
Future<Response> get(String path, {Map<String, dynamic>? queryParameters}) async {
try {
return await _dio.get(path, queryParameters: queryParameters);
} on DioException catch (e) {
throw _handleError(e);
}
}

// POST request
Future<Response> post(String path, {dynamic data}) async {
try {
return await _dio.post(path, data: data);
} on DioException catch (e) {
throw _handleError(e);
}
}

// PUT request
Future<Response> put(String path, {dynamic data}) async {
try {
return await _dio.put(path, data: data);
} on DioException catch (e) {
throw _handleError(e);
}
}

// DELETE request
Future<Response> delete(String path) async {
try {
return await _dio.delete(path);
} 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('Request timeout');
case DioExceptionType.badResponse:
return ServerException('Server error: ${error.response?.statusCode}');
case DioExceptionType.cancel:
return CancelException('Request cancelled');
default:
return NetworkException('Network error: ${error.message}');
}
}
}

// Custom exceptions
class TimeoutException implements Exception {
final String message;
TimeoutException(this.message);
}

class ServerException implements Exception {
final String message;
ServerException(this.message);
}

class CancelException implements Exception {
final String message;
CancelException(this.message);
}

class NetworkException implements Exception {
final String message;
NetworkException(this.message);
}

6️⃣ Kết Luận

Dio là lựa chọn tốt hơn so với HTTP package vì:

Tính năng phong phú: Interceptors, retry, transformers
Code sạch hơn: Dễ đọc, dễ bảo trì
Xử lý lỗi tốt hơn: DioException với nhiều thông tin
Linh hoạt: Hỗ trợ nhiều use cases
Cộng đồng lớn: Nhiều tài liệu và ví dụ
Được maintain tích cực: Cập nhật thường xuyên

💡 Lời khuyên: Nếu bạn đang bắt đầu dự án Flutter mới, hãy sử dụng Dio ngay từ đầu. Nếu đang dùng HTTP package, việc migrate sang Dio cũng không quá khó khăn và sẽ mang lại nhiều lợi ích.


🎓 Học Sâu Hơn Về Flutter

Muốn master Flutter, API Integration, và các best practices? Tham gia các khóa học tại Hướng Nghiệp Dữ Liệu:

📚 Khóa Học Liên Quan:


📝 Bài viết này được biên soạn bởi đội ngũ Hướng Nghiệp Dữ Liệu. Để cập nhật thêm về Flutter, Dio và các best practices trong phát triển ứng dụng di động, hãy theo dõi blog của chúng tôi.