Introduction to Dio: Setup & Basic Requests
Introduction to Dio: Setup & Basic Requests
The http package is simple and effective for basic networking, but real-world Flutter apps quickly demand more: automatic base URL configuration, centralized default headers, request/response interceptors, rich error models, and built-in support for form data and file uploads. Dio is a powerful HTTP client for Dart that covers all of these needs out of the box. In this lesson you will learn how to install and configure Dio, attach a base URL and default headers to a single Dio instance, and perform the same GET, POST, PUT, and DELETE requests you already know — using Dio's clean, consistent API.
Why Choose Dio Over the http Package?
Both packages can send HTTP requests, but they differ significantly in scope:
- Base URL & options: Dio accepts a
BaseOptionsobject at construction time, so you never have to concatenate the host into every request URL. - Interceptors: Dio has a first-class interceptor pipeline for logging, token injection, and automatic token refresh — no wrappers needed.
- Error handling: Network errors, 4xx/5xx responses, and timeouts all throw a typed
DioExceptionwith aDioExceptionTypeenum, making error branches straightforward. - FormData & file uploads: Dio ships
FormDataandMultipartFilewith no extra packages. - Cancellation: Requests can be cancelled via
CancelToken.
http package on all projects. For tiny apps or packages with minimal dependencies, the lighter http package is a fine choice. Dio shines when you need a production-ready HTTP client with advanced features.Step 1 — Adding Dio to Your Project
Open pubspec.yaml and add the dependency, then run flutter pub get:
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
dio: ^5.4.0 # use the latest stable version
After fetching packages, import Dio wherever you need it:
import 'package:dio/dio.dart';
Step 2 — Creating and Configuring a Dio Instance
The recommended pattern is to create a single, shared Dio instance (a singleton or a service class) and configure it once with BaseOptions. Every request made through that instance automatically inherits the base URL, headers, and timeouts.
import 'package:dio/dio.dart';
class ApiClient {
static final Dio _dio = Dio(
BaseOptions(
baseUrl: 'https://api.example.com/v1/',
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 15),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
),
);
// Expose the configured instance
static Dio get instance => _dio;
}
connectTimeout and receiveTimeout. Without them, a slow server can stall your app indefinitely. A good starting point is 10 seconds for connecting and 15 seconds for receiving.Step 3 — GET, POST, PUT, DELETE with Dio
Dio's methods mirror the HTTP verbs and return a Future<Response>. The Response object carries the parsed data, statusCode, headers, and more.
import 'package:dio/dio.dart';
class PostService {
final Dio _dio;
PostService(this._dio);
// GET — fetch a list of posts
Future<List<dynamic>> fetchPosts() async {
final Response response = await _dio.get('posts');
// response.data is already decoded JSON (List or Map)
return response.data as List<dynamic>;
}
// GET with query parameters
Future<Map<String, dynamic>> fetchPost(int id) async {
final Response response = await _dio.get(
'posts/$id',
queryParameters: {'include': 'comments'},
);
return response.data as Map<String, dynamic>;
}
// POST — create a new post
Future<Map<String, dynamic>> createPost(String title, String body) async {
final Response response = await _dio.post(
'posts',
data: {'title': title, 'body': body, 'userId': 1},
);
return response.data as Map<String, dynamic>;
}
// PUT — replace an existing post
Future<Map<String, dynamic>> updatePost(int id, String title) async {
final Response response = await _dio.put(
'posts/$id',
data: {'title': title, 'body': 'Updated body', 'userId': 1},
);
return response.data as Map<String, dynamic>;
}
// DELETE — remove a post
Future<void> deletePost(int id) async {
await _dio.delete('posts/$id');
}
}
http package, Dio automatically decodes the JSON response body into a Dart Map or List. You do not need to call jsonDecode() manually — the decoded result is available directly on response.data.Step 4 — Handling Errors with DioException
Dio throws a DioException (previously DioError in v4) for network failures, timeouts, and non-2xx responses. Its type property is a DioExceptionType enum that lets you branch cleanly:
Future<void> safeFetch() async {
try {
final response = await ApiClient.instance.get('posts/1');
print('Status: ${response.statusCode}');
print('Data: ${response.data}');
} on DioException catch (e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.receiveTimeout:
print('Request timed out.');
break;
case DioExceptionType.badResponse:
// Server replied with a non-2xx status
final status = e.response?.statusCode;
print('Server error: $status — ${e.response?.data}');
break;
case DioExceptionType.connectionError:
print('No internet connection.');
break;
default:
print('Unexpected error: ${e.message}');
}
}
}
DioError was renamed to DioException. If you are following older tutorials that catch DioError, update your catch clauses to DioException to avoid silent misses at runtime.Summary
In this lesson you learned how to add Dio to a Flutter project and configure a shared Dio instance with a base URL, default headers, and timeouts via BaseOptions. You then replicated the full set of CRUD requests — GET, POST, PUT, and DELETE — using Dio's concise verb methods, and handled errors through the typed DioException. Compared with the bare http package, Dio removes boilerplate (no manual jsonDecode, no URL concatenation, no header repetition) and lays the groundwork for interceptors and advanced features covered in the next lessons.