Flutter developers often rely on networking packages to communicate with web services and APIs. While the built-in http package suffices for simple tasks, the dio package provides more advanced features that are crucial for robust and scalable applications. These features include interceptors for request and response handling and powerful file download capabilities. This blog post explores using the dio package to enhance your Flutter apps.
Introduction to the Dio Package
Dio is a powerful HTTP client for Dart, which supports interceptors, global configuration, FormData, request cancellation, timeout, and file downloading, making it ideal for building networked applications in Flutter. Its versatility and ease of use make it a favorite among Flutter developers.
Why Use Dio Over the http Package?
Compared to the http package, dio offers several advantages:
- Interceptors: Ability to intercept and modify HTTP requests and responses globally.
- File Downloads: Streamlined and efficient file downloading.
- Request Cancellation: Ability to cancel pending requests.
- Global Configuration: Centralized configuration for base URLs, headers, and timeouts.
- FormData Support: Easy handling of form data.
Setting Up Dio in Your Flutter Project
First, add the dio dependency to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
dio: ^5.4.0 # Use the latest version
Then, run flutter pub get to install the package.
Basic Usage of Dio
Here’s a simple example to illustrate how to make a GET request with dio:
import 'package:dio/dio.dart';
void main() async {
final dio = Dio();
try {
final response = await dio.get('https://jsonplaceholder.typicode.com/todos/1');
print('Response data: ${response.data}');
} catch (e) {
print('Error: $e');
}
}
This code creates a new Dio instance, makes a GET request to a sample API, and prints the response data or any error that occurs.
Implementing Interceptors
Interceptors in dio are classes that can intercept requests and responses. This is particularly useful for adding headers, logging requests, or handling errors globally.
Creating a Custom Interceptor
Here’s how to create a custom interceptor that logs each request and response:
import 'package:dio/dio.dart';
class LoggingInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
print('REQUEST[${options.method}] => PATH: ${options.path}');
return super.onRequest(options, handler);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
print(
'RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}');
return super.onResponse(response, handler);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
print(
'ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path}');
return super.onError(err, handler);
}
}
Adding the Interceptor to Dio
To add the interceptor to your Dio instance, use the interceptors.add method:
void main() async {
final dio = Dio();
dio.interceptors.add(LoggingInterceptor());
try {
final response = await dio.get('https://jsonplaceholder.typicode.com/todos/1');
print('Response data: ${response.data}');
} catch (e) {
print('Error: $e');
}
}
Now, every request and response will be logged to the console, which aids in debugging and monitoring.
Handling Authentication with Interceptors
Interceptors can also manage authentication by adding an authorization header to each request. For example, let’s assume you need to add a token to the header:
class AuthInterceptor extends Interceptor {
final String token;
AuthInterceptor(this.token);
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
options.headers['Authorization'] = 'Bearer $token';
return super.onRequest(options, handler);
}
}
void main() async {
final dio = Dio();
final token = 'your_auth_token';
dio.interceptors.add(AuthInterceptor(token));
try {
final response = await dio.get('https://api.example.com/data');
print('Response data: ${response.data}');
} catch (e) {
print('Error: $e');
}
}
This interceptor adds the Authorization header to each request, ensuring that the application authenticates properly.
File Downloading with Dio
The dio package simplifies file downloading, allowing you to save files directly to the device.
Basic File Download
Here’s a simple example of how to download a file using dio:
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
void main() async {
final dio = Dio();
try {
final dir = await getApplicationDocumentsDirectory();
final path = '${dir.path}/example.pdf';
await dio.download(
'http://www.africau.edu/images/default/sample.pdf',
path,
onReceiveProgress: (received, total) {
if (total != -1) {
print('${(received / total * 100).toStringAsFixed(0)}%');
}
},
);
print('File downloaded to: $path');
} catch (e) {
print('Error: $e');
}
}
Explanation:
- First, it retrieves the application documents directory using
path_provider. - It constructs the full file path.
- The
dio.downloadmethod is used to download the file, taking the URL and the destination path as arguments. - The
onReceiveProgresscallback provides progress updates during the download.
Advanced File Download
You might want to handle exceptions and implement more advanced features, like resuming downloads or verifying file integrity. Here’s an enhanced example:
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
void downloadFile(String url, String fileName) async {
final dio = Dio();
try {
final dir = await getApplicationDocumentsDirectory();
final filePath = '${dir.path}/$fileName';
final response = await dio.get(
url,
onReceiveProgress: (received, total) {
if (total != -1) {
print('Downloading: ${(received / total * 100).toStringAsFixed(0)}%');
}
},
options: Options(
responseType: ResponseType.bytes,
followRedirects: false,
validateStatus: (status) {
return status != null && status < 500;
},
),
);
final file = File(filePath);
final raf = file.openSync(mode: FileMode.write);
raf.writeFromSync(response.data);
await raf.close();
print('File downloaded to $filePath');
} catch (e) {
print('Error downloading file: $e');
}
}
void main() {
downloadFile(
'http://www.africau.edu/images/default/sample.pdf', 'sample.pdf');
}
This improved version includes exception handling, custom validation, and direct writing to a file using File and RandomAccessFile, which offers better performance.
FormData with Dio
FormData is useful for sending multipart/form-data requests, such as when uploading files to a server.
import 'dart:io';
import 'package:dio/dio.dart';
void main() async {
final dio = Dio();
final formData = FormData.fromMap({
'name': 'John Doe',
'age': 30,
'file': await MultipartFile.fromFile(
'path/to/your/file.jpg',
filename: 'avatar.jpg',
),
});
try {
final response = await dio.post(
'https://api.example.com/upload',
data: formData,
);
print('Response data: ${response.data}');
} catch (e) {
print('Error: $e');
}
}
In this example:
- It creates a
FormDataobject and adds text fields (name,age) and a file field (file). - It then posts the
FormDatato an upload endpoint.
Conclusion
The dio package is an excellent choice for Flutter developers who need more advanced networking features than the basic http package provides. With features like interceptors and file downloading, dio offers the flexibility and power needed to build robust and scalable Flutter applications. Whether you need to add authentication headers, log requests, or efficiently download files, dio has you covered.