← Flutter Reference

Dio vs http

Networking

By Abdelrahman Saed — Senior Mobile Engineer. Last updated: 2026-08-10.

Quick answer

Use Dio for any app with non-trivial networking: interceptors, token refresh, file uploads, request cancellation, timeout policies, or retry logic. Use the http package for one-off scripts or tiny apps with a couple of GETs.

http is the official, minimal Dart HTTP client — fine and boring for simple cases. Dio is a richer client built for real apps: interceptors (auth, logging, retry), CancelToken, FormData for multipart, and consistent error handling via DioException. In a 50+ module production app, Dio's interceptor stack is load-bearing infrastructure.

Feature comparison

FeatureDiohttp
InterceptorsFirst-classNone (DIY wrapper)
CancellationCancelTokenNone
TimeoutsConfigurable per-requestGlobal only
Multipart / FormDataErgonomic + progressMultipartRequest (basic)
Retry logicInterceptor or built-inDIY
Auth / token refreshInterceptor patternManual per call
Error modelDioException with type enumClientException (basic)
Streaming responseYesYes (via HttpClient)
Best forReal apps with networking complexityScripts, tiny apps

Detailed comparison

Surface area

http is intentionally minimal: get, post, put, delete, head, patch. You get a Response with a body string/bytes. No interceptors, no cancellation tokens, no built-in retry.

Dio wraps a similar core with a much richer API: interceptors (request/response/error), CancelToken, FormData (multipart uploads), configurable timeouts, response type control (JSON, bytes, stream), and a typed DioException with a clear type enum.

Interceptors — the decisive feature

Interceptors are why production apps pick Dio. An auth interceptor attaches the bearer token and refreshes it on 401; a logging interceptor records every request for debugging; a retry interceptor retries transient failures. Composing these in one place beats scattering headers['Authorization'] = ... across every call site.

dio.interceptors.add(InterceptorsWrapper(
  onRequest: (options, handler) {
    options.headers['Authorization'] = 'Bearer $token';
    handler.next(options);
  },
  onError: (e, handler) async {
    if (e.response?.statusCode == 401) {
      await refreshToken();
      return handler.resolve(await dio.fetch(e.requestOptions)));
    }
    handler.next(e);
  },
));

With http, you reimplement this as a wrapper function and call it everywhere, or wrap every call manually.

Cancellation

Dio's CancelToken lets you cancel in-flight requests (e.g. when a user navigates away from a search screen). http has no cancellation primitive — you rely on the underlying HttpClient or ignore the result.

For typeahead/search UIs where stale responses must be discarded, cancellation is essential. We use it across every search-driven screen.

File uploads

Dio's FormData handles multipart uploads cleanly, including progress callbacks. http supports MultipartRequest but with less ergonomic progress reporting. For apps that upload images/documents (most consumer apps), Dio is noticeably smoother.

When http is fine

If your app makes five GETs to a public API with no auth, no uploads, and no cancellation needs, http is simpler and has zero extra dependencies. Do not reach for Dio for a prototype weather widget.

Logging and observability

In production, you need to see what your app is sending and receiving — for debugging user-reported issues and for performance monitoring. Dio's LogInterceptor can log full request/response bodies, headers, and timing, and you can gate verbosity by environment (verbose in dev, redacted in prod). Sentry's Dio integration automatically captures failed HTTP requests as breadcrumbs, so a crash report shows the exact API call that preceded it. With http, you build your own logging wrapper and there is no first-party Sentry breadcrumb integration — you manually capture the request context. For a 5M-user app where triaging a bug means correlating a crash with the API response that triggered it, Dio's observability story is a real operational advantage.

Code comparison

Authenticated GET with token refresh — Dio

final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
dio.interceptors.add(AuthInterceptor(refreshToken));

// every request gets auth, retries on 401, cancels on navigate-away
final r = await dio.get('/me', cancelToken: cancelToken);

Authenticated GET — http

// you must attach headers and handle refresh at every call site
final r = await http.get(
  Uri.parse('https://api.example.com/me'),
  headers: {'Authorization': 'Bearer $token'},
);
if (r.statusCode == 401) {
  await refreshToken();
  // ... retry manually
}
// no cancellation, no interceptor reuse

Dio centralizes cross-cutting concerns; http pushes them to every call site. For an app with auth, uploads, and cancellation needs, Dio is the clear production choice.

Which should you choose?

Choose Dio when: the app has auth/token refresh, file uploads, request cancellation, retry policies, or any cross-cutting networking concern. Interceptors make the networking layer maintainable. The default for real apps.

Choose http when: you are writing a script, a tiny app with a few unauthenticated GETs, or you want zero extra dependencies. Simpler is better when the complexity is not there.

Related definitions

Related reading

FAQ

Is Dio heavier than http?

Marginally in package size, negligible at runtime. The interceptor model has trivial overhead per request. For any app where networking is a first-class concern, the productivity gain vastly outweighs the footprint.

Can I use interceptors with http?

Not natively. You wrap calls in a helper function that does the auth/logging/retry. It works but scatters logic and is harder to compose than Dio's interceptor chain. At a certain complexity, migrating to Dio is cleaner.

Does Dio work with Retrofit codegen?

Yes — retrofit (the Dart package) generates type-safe API clients on top of Dio. That gives you typed API interfaces with all of Dio's interceptor/cancellation benefits. A great combo for a large API surface.

How do I test a repository that uses Dio?

Use Dio's built-in MockAdapter or inject a mock Dio instance into your repository. Because Dio is a concrete class with a clean interface, you mock it at the Dio level — set up when(dio.get('/path')) responses and assert on the calls. With http, you typically mock the http.Client interface, which works but means your test doubles model a lower-level abstraction. The practical difference: Dio's interceptor-aware mock lets you test the full request pipeline (including auth header injection) in a unit test, while mocking http.Client tests only the final request/response and leaves interceptor-equivalent logic untested.


Available for hire. Abdelrahman Saed is a Senior Mobile Engineer (Flutter) — open to full-time, fractional, contract, or advisory work. Hire me →

Book a 20-minute call · Download the CV (PDF) · See how I work