The dio
package is a powerful HTTP client for Flutter that provides a range of features for making network requests. While basic usage covers simple GET and POST requests, the true potential of dio
lies in its advanced features such as interceptors, request cancellation, and file downloading. Mastering these features allows for more robust, efficient, and user-friendly applications.
Introduction to dio
dio
supports various HTTP methods, request and response transformations, file uploads and downloads, timeouts, interceptors, and global configuration. Its flexible and extensible design makes it an ideal choice for complex network interactions in Flutter applications.
Why Use Advanced dio
Features?
- Interceptors: Allow you to intercept and modify HTTP requests and responses globally, useful for logging, authentication, or retrying failed requests.
- Cancellation: Provides the ability to cancel ongoing requests, which is crucial for optimizing network usage and improving user experience, especially in scenarios where users navigate away from a screen.
- File Download: Enables efficient file downloads with progress tracking, crucial for applications that need to handle large file transfers.
Prerequisites
Before diving into advanced usage, make sure you have the dio
package added to your pubspec.yaml
file:
dependencies:
flutter:
sdk: flutter
dio: ^5.3.0 # Use the latest version
Then, run flutter pub get
to install the package.
1. Using Interceptors
Interceptors in dio
allow you to intercept requests before they are sent and responses before they are delivered to your application. This can be used for global configurations, such as adding authentication headers, logging requests, or handling errors.
Implementing an Interceptor
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);
}
}
In this example, the LoggingInterceptor
logs the request method and path before sending the request, the response status code and path upon receiving a response, and any errors that occur during the request.
Adding Interceptors to dio
Instance
To use the interceptor, add it to your dio
instance:
import 'package:dio/dio.dart';
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');
}
}
2. Cancelling Requests
Being able to cancel HTTP requests is vital for improving the user experience. For instance, if a user navigates away from a screen before a request completes, you should cancel the request to free up resources.
Using a CancelToken
dio
provides a CancelToken
to cancel requests:
import 'package:dio/dio.dart';
void main() async {
final dio = Dio();
final cancelToken = CancelToken();
try {
Future.delayed(Duration(seconds: 5), () {
cancelToken.cancel('Operation cancelled due to timeout.');
});
final response = await dio.get(
'https://jsonplaceholder.typicode.com/todos/1',
cancelToken: cancelToken,
);
print('Response data: ${response.data}');
} on DioException catch (e) {
if (CancelToken.isCancel(e)) {
print('Request cancelled: ${e.message}');
} else {
print('Error: $e');
}
}
}
In this example, the request is cancelled after 5 seconds if it hasn’t completed. The CancelToken.isCancel(e)
checks if the error was due to cancellation.
Real-World Scenario: Search Feature
Consider a search feature where you want to cancel previous requests when the user types a new query. This prevents race conditions and outdated results from appearing.
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
class SearchScreen extends StatefulWidget {
@override
_SearchScreenState createState() => _SearchScreenState();
}
class _SearchScreenState extends State {
final dio = Dio();
CancelToken? _cancelToken;
String _searchResults = '';
Future _search(String query) async {
if (_cancelToken != null) {
_cancelToken!.cancel('New search started');
}
_cancelToken = CancelToken();
try {
final response = await dio.get(
'https://api.example.com/search?q=$query', // Replace with your API endpoint
cancelToken: _cancelToken,
);
setState(() {
_searchResults = response.data.toString(); // Adjust based on your API response
});
} on DioException catch (e) {
if (CancelToken.isCancel(e)) {
print('Search cancelled: ${e.message}');
} else {
print('Search error: $e');
}
} finally {
_cancelToken = null; // Reset cancel token after completion/cancellation
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Search')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
onChanged: (text) {
_search(text);
},
decoration: InputDecoration(labelText: 'Search Query'),
),
SizedBox(height: 20),
Text('Results: $_searchResults'),
],
),
),
);
}
}
3. File Download with Progress Tracking
Downloading files efficiently and providing users with progress updates is essential for applications that handle file transfers. dio
makes it easy to download files and track the progress.
Downloading a File
import 'package:dio/dio.dart';
import 'dart:io';
void main() async {
final dio = Dio();
final url = 'https://www.example.com/large_file.zip'; // Replace with your file URL
final savePath = '/path/to/save/file.zip'; // Replace with your desired path
try {
await dio.download(
url,
savePath,
onReceiveProgress: (received, total) {
if (total != -1) {
print('${(received / total * 100).toStringAsFixed(0)}%');
}
},
);
print('File downloaded successfully!');
} catch (e) {
print('Error downloading file: $e');
}
}
The dio.download
method takes the URL of the file and the path where you want to save it. The onReceiveProgress
callback provides the number of bytes received and the total file size, allowing you to calculate and display the download progress.
Displaying Download Progress in UI
Here’s how to display the download progress in a Flutter UI:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:path_provider/path_provider.dart'; // Add this dependency
class DownloadScreen extends StatefulWidget {
@override
_DownloadScreenState createState() => _DownloadScreenState();
}
class _DownloadScreenState extends State {
final dio = Dio();
double _downloadProgress = 0.0;
bool _isDownloading = false;
String _filePath = '';
Future _downloadFile() async {
setState(() {
_isDownloading = true;
_downloadProgress = 0.0;
});
try {
final url = 'https://www.easygifanimator.net/images/samples/video-to-gif-sample.gif'; // Replace with a larger file
final dir = await getApplicationDocumentsDirectory();
_filePath = '${dir.path}/my_downloaded_file.gif'; // File name example
await dio.download(
url,
_filePath,
onReceiveProgress: (received, total) {
if (total != -1) {
setState(() {
_downloadProgress = received / total;
});
}
},
);
print('File downloaded successfully!');
} catch (e) {
print('Error downloading file: $e');
} finally {
setState(() {
_isDownloading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('File Download')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_isDownloading)
Column(
children: [
Text('Downloading... ${(_downloadProgress * 100).toStringAsFixed(0)}%'),
SizedBox(height: 10),
LinearProgressIndicator(value: _downloadProgress),
],
)
else
ElevatedButton(
onPressed: _downloadFile,
child: Text('Download File'),
),
if(_filePath.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Text("File saved to: $_filePath"),
)
],
),
),
);
}
}
Key points:
- Use
getApplicationDocumentsDirectory()
to get a suitable location for the downloaded file. Make sure to addpath_provider
to yourpubspec.yaml
. - The UI updates based on
_downloadProgress
, showing aLinearProgressIndicator
during the download. - Error handling ensures the UI is updated correctly if the download fails.
Conclusion
The dio
package in Flutter provides a robust set of features for handling network requests efficiently. By leveraging interceptors, cancellation tokens, and file download capabilities, you can build more responsive, reliable, and user-friendly applications. These advanced techniques allow you to manage network operations more effectively, providing a better overall experience for your users.