Setting up Dio for network requests in Flutter applications
The http package from the Dart ecosystem covers basic needs, but production applications quickly face tasks it solves awkwardly: authorization interceptors, automatic retry, logging, request cancellation, multipart file uploads with progress. Dio covers all this out of the box.
Installation and basic configuration
dependencies:
dio: ^5.4.0
Create a singleton via get_it or riverpod:
final dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com/v1',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 30),
headers: {'Accept': 'application/json'},
));
Interceptors — key part of configuration
Auth interceptor adds token to each request and handles 401:
class AuthInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
final token = tokenStorage.accessToken;
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 401) {
try {
await tokenStorage.refresh();
// Retry original request with new token
final opts = err.requestOptions;
opts.headers['Authorization'] = 'Bearer ${tokenStorage.accessToken}';
final response = await dio.fetch(opts);
handler.resolve(response);
return;
} catch (_) {
// refresh failed — logout
}
}
handler.next(err);
}
}
Logging in dev mode:
if (kDebugMode) {
dio.interceptors.add(LogInterceptor(
requestBody: true,
responseBody: true,
logPrint: (o) => debugPrint(o.toString()),
));
}
Retry interceptor for network errors — use dio_smart_retry:
dio.interceptors.add(RetryInterceptor(
dio: dio,
retries: 3,
retryDelays: [
Duration(seconds: 1),
Duration(seconds: 2),
Duration(seconds: 3),
],
));
File uploads and request cancellation
// Multipart upload with progress
final formData = FormData.fromMap({
'file': await MultipartFile.fromFile(filePath, filename: 'photo.jpg'),
});
await dio.post('/upload', data: formData,
onSendProgress: (sent, total) {
progress.value = sent / total;
},
);
// Cancel request
final cancelToken = CancelToken();
dio.get('/data', cancelToken: cancelToken);
// Later:
cancelToken.cancel('User navigated away');
CancelToken is mandatory for requests tied to widget lifecycle. Not canceling requests in dispose() — memory leak and possible setState after dispose.
Error handling
DioException contains type — important to distinguish:
-
DioExceptionType.connectionTimeout— no internet or server unavailable -
DioExceptionType.badResponse— server returned 4xx/5xx -
DioExceptionType.cancel— request canceled
Wrap in domain layer so you don't drag Dio dependency into BLoC/Cubit:
Future<Either<Failure, T>> safeCall<T>(Future<T> Function() request) async {
try {
return Right(await request());
} on DioException catch (e) {
return Left(NetworkFailure.fromDioException(e));
}
}
Timelines
Basic Dio setup with auth interceptor and logging: 4–8 hours. With retry, error handling and architecture integration (BLoC, Riverpod): 1–2 days. Pricing calculated individually.







