|claim|login
RepoCritics — Review. Share. Archive. Every open-source repo.

cfug/dio

Wiki: cfug/dio

Source: https://github.com/cfug/dio

Last synced 2026-07-16 · 993 words · Edit wiki on GitHub →

cfug/dio

> The de facto HTTP client for Dart and Flutter when the standard package:http is too thin.

GitHub repo · Official website · License: MIT

Overview

dio is an HTTP client library for Dart and Flutter. Where the official package:http is deliberately minimal (send a request, get a response), dio targets the layer above that: interceptors, FormData multipart uploads, request cancellation, per-request and per-client timeouts, streamed downloads, response transformers, and swappable transport adapters — all configured through a single Dio instance1. It is the client most Flutter apps reach for once they outgrow one-off http.get calls.

The project was originally authored by @wendux under the @flutterchina organization, and has been maintained by the Chinese Flutter User Group (@cfug) since 20232. That handover coincided with the 5.0 release, which restructured the codebase into the current monorepo: the core dio package plus first-party plugins for cookies, HTTP/2, native platform transports, and web3.

The defining tension is scope. dio's convenience — global config, an interceptor chain, automatic JSON decoding — is exactly what makes it heavier and more opinionated than package:http. Teams that want a small, auditable dependency prefer the standard client; teams that want an axios-style batteries-included experience prefer dio. It is not a wrapper over package:http; it sits directly on dart:io's HttpClient (and fetch on web) through its own adapter abstraction.

Getting Started

# pubspec.yaml
dependencies:
  dio: ^5.0.0

Or dart pub add dio / flutter pub add dio.

import 'package:dio/dio.dart';

final dio = Dio(BaseOptions(
  baseUrl: 'https://api.example.com',
  connectTimeout: const Duration(seconds: 5),
  receiveTimeout: const Duration(seconds: 3),
));

Future<void> main() async {
  try {
    final res = await dio.get<Map<String, dynamic>>('/users/1');
    print(res.data?['name']); // JSON is decoded automatically
  } on DioException catch (e) {
    // e.type distinguishes timeout / bad status / cancel / connection error
    print('${e.type}: ${e.response?.statusCode}');
  }
}

Architecture / How It Works

A Dio instance is a façade over three composable pieces:

1. Interceptors — an ordered chain run around every request. Each Interceptor (or InterceptorsWrapper) can mutate the request in onRequest, short-circuit with a cached Response in onResponse, or convert/swallow errors in onError. Control flow is explicit via a handler: handler.next(...) continues the chain, handler.resolve(...) ends it with a response, handler.reject(...) ends it with an error. QueuedInterceptor serializes its callbacks, which is the standard pattern for token refresh (hold requests while one refresh is in flight). 2. HttpClientAdapter — the transport boundary. The default is IOHttpClientAdapter, wrapping dart:io's HttpClient; on web the dio_web_adapter uses the browser fetch/XMLHttpRequest. Swapping the adapter is how you get HTTP/2 (dio_http2_adapter) or native platform stacks (native_dio_adapter → Cronet on Android, NSURLSession on iOS via cronet_http/cupertino_http)3. 3. Transformer — encodes request bodies and decodes response bodies. The default BackgroundTransformer offloads JSON parsing to a background isolate so large payloads don't jank the UI thread; SyncTransformer parses inline.

Options are layered: BaseOptions on the client are merged with per-call Options, producing the RequestOptions that travel through the chain. Response<T> carries the decoded data, headers, and status; ResponseType (json, stream, bytes, plain) selects how the body is materialized. A CancelToken passed to one or more requests lets you abort them together.

Production Notes

DioException is the error surface, not exceptions from dart:io. Everything failing goes through DioException with a type (connectionTimeout, sendTimeout, receiveTimeout, badResponse, cancel, connectionError, badCertificate, unknown). By default validateStatus throws on any non-2xx status, so a 404 is a caught exception, not a returned response — code that expects to inspect res.statusCode for 4xx must override validateStatus or read e.response in the catch.

FormData is single-use. Its underlying streams are consumed on send, so reusing the same FormData for a retry (or in a retry interceptor) throws. Use formData.clone() to build a fresh copy per attempt.

Timeouts are per-phase, not a total budget. connectTimeout, sendTimeout, and receiveTimeout each bound a segment of the request; there is no single "whole request must finish in N seconds" option. A slow-drip response that keeps delivering bytes can outlive any individual timeout. Wrap the future with Future.any / a timer if you need a hard ceiling.

Web is a reduced feature set. On the web adapter you cannot set some restricted headers, HttpClient-level customization (proxies, bad-cert callbacks, connection tuning) does not apply, and cross-origin credentials depend on withCredentials plus server CORS. Code that configures the IO adapter must guard those paths with kIsWeb.

Interceptor ordering and re-entrancy bite. Interceptors run in registration order on the way out and reverse on the way back; forgetting to call handler.next silently stalls the request. A refresh interceptor that issues its own dio.request(...) will re-enter the same chain unless it uses a separate Dio instance or a QueuedInterceptor, causing infinite loops.

Upgrades follow a documented migration guide. dio warns that breaking changes can land in both major and minor versions, and maintains a Migration Guide and Compatibility Policy rather than promising SemVer patch-level stability1. Pin versions and read the changelog before bumping.

When to Use / When Not

Use when:

  • You need interceptors (auth, logging, retry, caching) applied globally.
  • You upload multipart forms or stream large downloads with progress.
  • You want per-request cancellation via CancelToken.
  • You may need to swap the transport (HTTP/2, native Cronet/NSURLSession) later.

Avoid when:

  • Your needs are a handful of GETs/POSTs — package:http is smaller and audited.
  • You want minimal dependency surface / strict SemVer stability.
  • You are on web-only and want the thinnest possible fetch wrapper.
  • You prefer generated, annotation-based API clients over an imperative client.

Alternatives

  • dart-lang/http — the official, minimal Dart HTTP client; no interceptors or FormData sugar. Use it when you want a small, well-audited dependency.
  • lejard-h/chopper — Retrofit-style, code-generated API client built on package:http. Use it when you prefer annotation-driven, type-safe endpoints.
  • trevorwang/retrofit.dart — annotation-based client generator that runs on top of dio. Use it when you want dio's features but generated call sites.
  • flutterchina/dio (legacy) — the pre-2023 org/location; use cfug/dio, which is the maintained continuation.

History

VersionDateNotes
initial2018-04Repo created under @flutterchina by @wendux2.
3.x2019–2020Null-safety pre-work era; widely adopted in Flutter apps.
4.02021Null-safe release.
5.02023Maintenance moved to @cfug; monorepo restructure, plugins split out3.

References

  1. ^ dio README and package documentation — features, versioning, and Compatibility Policy. https://pub.dev/packages/dio
  2. ^ Repository metadata and copyright notice: originally authored by @wendux under @flutterchina, maintained by the Chinese Flutter User Group (@cfug) since 2023. https://github.com/cfug/dio
  3. ^ dio monorepo plugin packages — dio_cookie_manager, dio_http2_adapter, native_dio_adapter, dio_web_adapter, dio_compatibility_layer. https://github.com/cfug/dio/tree/main/plugins

Tags

dart, flutter, http-client, networking, interceptors, formdata, rest-api, mobile, cross-platform, cancellation