Dio HTTP Client
Professional Networking for Flutter
Open interactive version (quiz + challenge)Real-world analogy
If the basic http package is like sending letters by regular mail, Dio is like having a personal assistant who handles all your mail. Your assistant (Dio) adds your return address to every letter (base URL), stamps them all (auth headers via interceptors), keeps copies (logging), retries if the mailman loses one (retry interceptor), and even opens the replies for you (response transformation)!
What is it?
Dio is a powerful HTTP client for Dart used as the networking layer in team_mvp_kit. It provides interceptors for automatic token injection and refresh, configurable timeouts, request cancellation, detailed error typing, logging, and a clean API for all HTTP methods. Every request flows through a middleware pipeline of interceptors.
Real-world relevance
In team_mvp_kit, Dio is configured once in the DI module with base URL, timeouts, and interceptors. The AuthInterceptor attaches tokens and refreshes expired ones. The ApiService wraps Dio with typed methods so repositories never touch raw HTTP. Every API call flows through: Dio -> AuthInterceptor -> LogInterceptor -> Server -> Response.
Key points
- Why Dio Over http Package? — Dio is a powerful HTTP client for Dart that goes beyond the basic http package. It supports interceptors (middleware for every request), request cancellation, file uploading with progress, timeout configuration, and automatic JSON transformation. The team_mvp_kit uses Dio as its core networking layer.
- Dio Setup and BaseOptions — Configure Dio once with BaseOptions that apply to every request: base URL, timeouts, default headers, and content type. In team_mvp_kit, the Dio instance is created in a @module and shared across all services via dependency injection.
- Making Requests with Dio — Dio provides methods for all HTTP verbs: get(), post(), put(), patch(), delete(). Pass path strings (not full URLs) because the baseUrl is prepended automatically. Post data as Maps and Dio serializes them to JSON for you.
- Interceptors - Middleware for HTTP — Interceptors run code before every request, after every response, and on every error. Add auth tokens automatically, log requests for debugging, or retry failed requests. team_mvp_kit uses interceptors for token injection, token refresh, and logging.
- Auth Interceptor Pattern — The most important interceptor in team_mvp_kit automatically attaches the access token to every request. If a request fails with 401 (Unauthorized), it refreshes the token and retries. This gives seamless auth without manual token management in every API call.
- Error Handling with DioException — Dio wraps all errors in DioException with specific types: connectionTimeout, receiveTimeout, badResponse (4xx/5xx), connectionError (no internet), cancel (request cancelled). Handle each type to show appropriate messages to users.
- Logging Interceptor — Add a LogInterceptor during development to see every request and response in the console. Invaluable for debugging API issues. Disable it in production to avoid leaking sensitive data.
- The ApiService in team_mvp_kit — team_mvp_kit wraps Dio in an ApiService class that provides typed methods like get, post, put, and delete. This centralized service adds the auth interceptor, logging, and error mapping so repositories never deal with raw Dio.
- Request Cancellation — Use CancelToken to abort requests that are no longer needed, like when a user navigates away before data finishes loading. This prevents wasted bandwidth and avoids updating state on a disposed widget.
Code example
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
// 1. Dio Module - configured once, injected everywhere
@module
abstract class NetworkModule {
@singleton
Dio dio(TokenStorage tokenStorage) {
final dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com/v1',
connectTimeout: const Duration(seconds: 15),
receiveTimeout: const Duration(seconds: 15),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
));
dio.interceptors.addAll([
AuthInterceptor(tokenStorage, dio),
LogInterceptor(
requestBody: true,
responseBody: true,
logPrint: (msg) => debugPrint(msg.toString()),
),
]);
return dio;
}
}
// 2. Auth Interceptor - auto-attaches and refreshes tokens
class AuthInterceptor extends Interceptor {
final TokenStorage _tokenStorage;
final Dio _dio;
AuthInterceptor(this._tokenStorage, this._dio);
@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 error,
ErrorInterceptorHandler handler,
) async {
if (error.response?.statusCode == 401) {
final refreshed = await _tokenStorage.refreshTokens();
if (refreshed) {
error.requestOptions.headers['Authorization'] =
'Bearer ${_tokenStorage.accessToken}';
final response = await _dio.fetch(error.requestOptions);
return handler.resolve(response);
}
}
handler.next(error);
}
}
// 3. ApiService - typed wrapper used by repositories
@lazySingleton
class ApiService {
final Dio _dio;
ApiService(this._dio);
Future<Map<String, dynamic>> get(
String path, {
Map<String, dynamic>? queryParams,
}) async {
final response = await _dio.get(
path, queryParameters: queryParams,
);
return response.data as Map<String, dynamic>;
}
Future<Map<String, dynamic>> post(
String path, {
Map<String, dynamic>? data,
}) async {
final response = await _dio.post(path, data: data);
return response.data as Map<String, dynamic>;
}
}Line-by-line walkthrough
- 1. Import the Dio package
- 2. Import injectable for dependency injection annotations
- 3.
- 4. Comment: Dio is configured once in a module
- 5. @module annotation for registering third-party classes
- 6. Abstract class for the network module definition
- 7. @singleton ensures one Dio instance for the whole app
- 8. Method that creates and configures Dio, receiving TokenStorage
- 9. Create a new Dio instance with BaseOptions
- 10. Set the base URL that is prepended to all request paths
- 11. Set connection timeout to 15 seconds
- 12. Set receive timeout to 15 seconds
- 13. Opening default headers map
- 14. All requests send JSON content type
- 15. All requests accept JSON responses
- 16. Closing headers and BaseOptions
- 17.
- 18. Add both interceptors to Dio's pipeline
- 19. AuthInterceptor handles token injection and refresh
- 20. LogInterceptor prints requests and responses for debugging
- 21. Closing the addAll call
- 22.
- 23. Return the fully configured Dio instance
- 24. Closing the dio method and NetworkModule
- 25.
- 26. Comment: AuthInterceptor handles authentication automatically
- 27. Class extending Dio's Interceptor base class
- 28. Private field for token storage
- 29. Private field for Dio (needed for retry requests)
- 30.
- 31. Constructor receives both dependencies
- 32.
- 33. Override onRequest which runs before every outgoing request
- 34. Method signature with RequestOptions and handler
- 35. Get the current access token from storage
- 36. If we have a token, add it to the Authorization header
- 37. Setting the Bearer token header value
- 38. Closing the if block
- 39. Continue to the next interceptor or send the request
- 40. Closing onRequest
- 41.
- 42. Override onError which runs on every failed response
- 43. Method signature with DioException and handler
- 44. Check if the error was a 401 Unauthorized response
- 45. Try to refresh the expired token via token storage
- 46. If refresh succeeded, update the header on the original request
- 47. Set the new token value
- 48. Retry the original request using Dio fetch
- 49. Return the successful retry response to the caller
- 50. Closing the refresh success and 401 check blocks
- 51. If not 401 or refresh failed, pass the error along
- 52. Closing onError and AuthInterceptor class
- 53.
- 54. Comment: ApiService is the typed wrapper used by repositories
- 55. @lazySingleton means one instance, created on first use
- 56. ApiService class declaration
- 57. Private Dio field for making requests
- 58. Constructor receives Dio via injection
- 59.
- 60. GET method returns a parsed JSON map
- 61. Takes a path and optional query parameters
- 62. Send GET request via Dio with query parameters
- 63. Closing the get call
- 64. Cast and return response data as Map
- 65. Closing get method
- 66.
- 67. POST method returns a parsed JSON map
- 68. Takes a path and optional data body
- 69. Send POST request via Dio with data
- 70. Cast and return response data as Map
- 71. Closing post method and ApiService class
Spot the bug
final dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
));
final response = await dio.get('https://api.example.com/users');
print(response.data);Need a hint?
Look at the URL being passed to dio.get when a baseUrl is already configured...
Show answer
When baseUrl is set, dio.get() expects a relative path like '/users', not the full URL. Passing the full URL causes Dio to use it as-is, making baseUrl pointless. Worse, if the domains differ you hit the wrong server. Fix: change to dio.get('/users').
Explain like I'm 5
Imagine you send lots of letters to different people. With regular mail, you write your address, stick a stamp, and seal every single letter yourself. Dio is like hiring a postal assistant who does all that automatically. You just say 'send this to Bob' and the assistant adds your address, stamps it, logs it, and if the letter gets lost, sends it again without you even knowing!
Fun fact
Dio is the most popular HTTP package in the Flutter ecosystem with over 12,000 GitHub stars. The name comes from the Italian word for 'God' -- fitting because it handles networking with seemingly divine power compared to the basic http package!
Hands-on challenge
Create a Dio instance with BaseOptions (base URL, 10-second timeout). Add an InterceptorsWrapper that logs every request URL and every response status code. Make a GET request to https://jsonplaceholder.typicode.com/posts/1 and print the title from the response.
More resources
- Dio Package (pub.dev)
- Dio GitHub Repository (GitHub)
- Flutter Networking with Dio (Flutter Official)