Dio: What It Is and Key Features of the HTTP Client for Flutter

Author: IT Sectr Published: 2026-03-07 Reading time: 8 min

Dio is a powerful HTTP client for Dart and Flutter, created by Chinese engineer Wenda Wang. The library provides an advanced API with support for interceptors, FormData, file uploads, and request cancellation. According to pub.dev, 2025, Dio is the most popular HTTP client in the Flutter ecosystem with over 8 thousand stars on GitHub.

Key Takeaways

  • Dio — a powerful HTTP client for Dart and Flutter with interceptors and transformers
  • Interceptors — a mechanism for intercepting requests, responses, and errors for logging and authorization
  • FormData — built-in support for multipart/form-data for file uploads
  • Request Cancellation — CancelToken allows interrupting running requests at any time
  • Transformers — custom data transformation before sending and after receiving

What is Dio?

Dio is a powerful HTTP client library for the Dart language, most widely used in Flutter applications. Dio provides a rich API with support for interceptors, global configuration, transformers, FormData, file uploads, and flexible timeout management, making it the primary choice for networking in the Flutter community.

The library was created by Wenda Wang in 2018 as an alternative to the built-in dart:io HttpClient, which lacked many modern features: unified configuration for all requests, interceptors, and automatic serialization. By 2025, Dio surpassed the http package from the Dart team in popularity, taking first place among HTTP clients in the Flutter ecosystem according to pub.dev.

Dio supports three adapters: DartNativeAdapter (default on Android, iOS, Desktop), BrowserAdapter (on Web), and IOAdapter. The adapter is automatically selected depending on the platform. Dio also provides a unified interface for all Flutter platforms — Android, iOS, Web, macOS, Windows, and Linux.

How Dio Works

Dio's architecture is built on a handler chain. Each request passes through a sequence of interceptors that can modify the request (InterceptorsWrapper.onRequest), the response (onResponse), or handle an error (onError). After the interceptors, the request goes to transformers (Transformer), which transform the data before sending.

A Dio instance is configured through a BaseOptions object containing the base URL, default headers, timeouts, response type (JSON, stream, plain), query parameters, and data format. These settings apply to all requests but can be overridden in a specific request. BaseOptions provides a single configuration point for the entire application, simplifying endpoint changes or adding global headers.

Each Dio request returns Response<T>, where T is the data type after transformer processing. By default, Dio automatically converts JSON responses to Map<String, dynamic>. For typed responses, Dio is used together with serialization packages: json_serializable, freezed, or built_value. Response contains data, headers, statusCode, requestOptions, and extra data.

Dio Global Configuration

Basic configuration is created via Dio(BaseOptions). You can set a baseUrl for all requests, connectTimeout and receiveTimeout, content-type and accept headers, as well as queryParameters. All these parameters apply to every request, eliminating code duplication and centralizing network settings management.

Dio supports two serialization modes: JSON by default (responseType: ResponseType.json) and streaming (ResponseType.stream). In stream mode, Response.data returns a ResponseBody that can be read in chunks. This is convenient for large payload files where loading everything into memory is undesirable. The plain mode returns a raw string without automatic JSON parsing.

Dio Interceptors

Interceptors are Dio's key mechanism for intercepting and modifying requests, responses, and errors. They fully replace OkHttp's Interceptor and Ktor's plugins, but with a Dart-specific API and async support via Future. Interceptors can be added both in the global Dio configuration and for individual requests.

Interceptor MethodPurposeUsage Example
onRequestModify request before sendingAdding an authorization token
onResponseHandle successful responseConverting data to DTO objects
onErrorHandle request errorAutomatic retry on 503

LogInterceptor

The built-in LogInterceptor logs every request: method, URL, headers, body, and execution time. It has two modes: compact (one line per request) and full (complete information with body). LogInterceptor is especially useful during development, but it is recommended to disable it in release builds using conditional imports or a global flag.

Custom interceptors are created through the InterceptorsWrapper class. You can override one, two, or all three methods (onRequest, onResponse, onError). Dio executes interceptors strictly in the order they are added to the interceptors list. If an interceptor does not call handler.next(), the chain is interrupted, and the response/error does not reach the application.

For authentication in Dio, an interceptor is used that adds a Bearer token to the Authorization header. If the server returns 401, the interceptor in onError attempts to refresh the token via a refresh request and retries the original request with the new token. This pattern is called token refresh interceptor and is implemented through DioException by checking response?.statusCode == 401.

Dio provides built-in support for retry logic via the dio_smart_retry package or a custom RetryInterceptor. Retry is important for mobile applications: when the connection is lost for 2-3 seconds, Dio throws a DioException with type connectionTimeout or connectionError. RetryInterceptor catches this exception and retries the request up to 3 times with exponential backoff (1s, 2s, 4s), improving application reliability in unstable network conditions.

Dio Code Examples in Dart

Let's look at a basic GET request using Dio. An instance is created with BaseOptions, setting the base URL and timeouts. The request is executed via the get() method, returning a Response with data in Map format.

dart
final dio = Dio(BaseOptions(
    baseUrl: 'https://api.github.com',
    connectTimeout: Duration(seconds: 15),
    receiveTimeout: Duration(seconds: 15),
    headers: {
        'Accept': 'application/vnd.github.v3+json',
    },
))

final response = await dio.get('/users/octocat')
print(response.data['login'])

For a POST request with a JSON body, a Map object or a custom DTO is passed. Dio automatically serializes the Map to JSON via jsonEncode. For typed DTOs, the queryParameters option, data field, or a custom Transformer is used.

dart
final data = {
    'name': 'my-project',
    'description': 'Created via Dio',
    'private': false,
}

final response = await dio.post(
    '/user/repos',
    data: data,
    options: Options(
        contentType: ContentType.json.value,
    ),
)

print(response.data['id'])

Adding an Authorization Interceptor

A custom interceptor adds a Bearer token to every request. The onRequest method fires before sending, modifying the headers. On a 401 response, the interceptor can refresh the token and retry the request using the dio.fetch(requestOptions) method.

dart
class AuthInterceptor extends InterceptorsWrapper {
    final String token

    AuthInterceptor(this.token)

    @override
    void onRequest(
        RequestOptions options,
        RequestInterceptorHandler handler,
    ) {
        options.headers['Authorization'] = 'Bearer $token'
        handler.next(options)
    }
}

dio.interceptors.add(AuthInterceptor('ghp_abc123'))

File Upload and Download with Dio

Dio simplifies file uploads through FormData. To send a file, a MultipartFile is created from File, Bytes, or AssetBundle. FormData automatically sets the multipart/form-data header with the correct boundary and encoding. Dio supports upload progress via onSendProgress.

For file downloads, the download() method is used, which saves the data stream directly to a file. Dio supports resuming interrupted downloads via the Range header, which is especially useful for large files. Download progress is tracked through onReceiveProgress, allowing you to display a progress bar in the UI.

dart
final formData = FormData.fromMap({
    'file': await MultipartFile.fromFile(
        '/path/to/photo.jpg',
        filename: 'photo.jpg',
    ),
    'description': 'Profile photo',
})

await dio.post(
    '/upload',
    data: formData,
    onSendProgress: (sent, total) {
        final progress = sent / total * 100
        print('Upload: $progress%')
    },
)

// File download
await dio.download(
    'https://example.com/file.zip',
    '/storage/emulated/0/Download/file.zip',
    onReceiveProgress: (received, total) {
        print('Download: ${received / total * 100}%')
    },
)

Common Mistakes When Working with Dio

Incorrect error handling is the most common issue. Dio throws a DioException (formerly DioError) for any problems: network unavailability, timeout, HTTP errors 4xx/5xx. Many developers only catch generic Exception, losing information about the error type and the ability to handle it specifically. Use DioException.type to determine the cause of the failure.

Ignoring CancelToken leads to request leaks. If a user leaves a screen while a request is still running, Dio wastes resources and may attempt to update a destroyed State. Always create a CancelToken for each request and cancel it in dispose(). CancelToken generates a DioException with type cancel, which must be handled properly.

Lack of retry logic for temporary failures. On mobile devices, the network is often briefly unavailable. Implement an interceptor with automatic request retry on timeout or 503/502 response. Use RetryInterceptor from the dio_smart_retry package or write a custom interceptor with exponential backoff between attempts.

Frequently Asked Questions

How is Dio different from the Dart http package?

Dio provides interceptors, global BaseOptions configuration, FormData, upload progress, and CancelToken. The http package from the Dart team is minimalistic, without interceptors or global configuration. Dio is used in large projects, while http is used for simple scripts.

How to serialize JSON in Dio?

By default, Dio converts JSON to Map using jsonDecode. For typed serialization, use the json_serializable or freezed packages. Create a custom interceptor that converts response.data to DTO via fromJson() in onResponse.

How to cancel a request in Dio?

Create a CancelToken and pass it in the request options. Calling token.cancel() interrupts the request and throws a DioException with type cancel. CancelToken supports canceling multiple requests simultaneously, which is convenient for canceling all requests when leaving a screen.

Does Dio work on all Flutter platforms?

Yes, Dio works on all six Flutter platforms: Android, iOS, Web, macOS, Windows, and Linux. Each platform uses an adaptive HTTP client: DartNativeAdapter (native platforms) and BrowserAdapter (Web). A unified API for all platforms is a key advantage of Dio in Flutter projects.

How does Dio handle cookies?

Dio does not manage cookies automatically. For cookie support, use the dio_cookie_manager package together with cookie_jar. CookieManager intercepts Set-Cookie and Cookie headers and saves cookies in PersistCookieJar for automatic sending in subsequent requests to the same domain.

Summary

  • Dio — the most popular HTTP client in Flutter with interceptors and transformers
  • Interceptors onRequest, onResponse, and onError modify requests and responses
  • FormData and MultipartFile simplify file uploads to the server
  • CancelToken properly cancels requests to prevent memory leaks
  • BaseOptions centralizes URL, header, and timeout configuration
  • DioException contains the error type for detailed failure handling
  • Progress onSendProgress and onReceiveProgress display upload status

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also