Introduction
When building modern mobile applications using Flutter, interacting with APIs is a core requirement. Whether you're fetching user data, submitting forms, or integrating third-party services, efficient API handling becomes critical for performance and user experience. One of the most powerful and flexible libraries for handling HTTP requests in Flutter is Dio. Compared to the basic HTTP package, Dio offers advanced features like interceptors, global configuration, request cancellation, file downloading, and more.
In this guide, you’ll learn step-by-step how to handle API calls in Flutter using Dio, explained in simple and practical terms with real-world examples.
What is Dio in Flutter?
Dio is a third-party HTTP client for Flutter that simplifies network requests and provides advanced capabilities like:
Real-life analogy: Think of Dio as a smart delivery manager. Instead of just sending a parcel (API request), it tracks, logs, retries, and manages everything efficiently.
Step 1: Add Dio Dependency
First, you need to include Dio in your Flutter project.
Add this to your pubspec.yaml file:
dependencies:
dio: ^5.0.0
Then run:
flutter pub get
Step 2: Import Dio Package
Now import Dio in your Dart file where API calls will be handled:
import 'package:dio/dio.dart';
Step 3: Create a Dio Instance
Create a reusable Dio instance for better scalability.
final Dio dio = Dio(
BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 3),
),
);
Real-world example: Instead of setting address and rules every time you order something, you store them once.
Step 4: Make a GET API Call
Future<void> fetchUsers() async {
try {
final response = await dio.get('/users');
print(response.data);
} catch (e) {
print('Error: $e');
}
}
Before vs After:
Before: Manual request handling, limited control
After: Structured, scalable, and easy-to-manage API calls
Step 5: Make a POST API Call
Future<void> createUser() async {
try {
final response = await dio.post(
'/users',
data: {
'name': 'John',
'email': '[email protected]',
},
);
print(response.data);
} catch (e) {
print('Error: $e');
}
}
Real-life example: Submitting a form in an app like a signup page.
Step 6: Add Interceptors (Very Important)
Interceptors help you log requests, add tokens, and handle errors globally.
dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
options.headers['Authorization'] = 'Bearer YOUR_TOKEN';
return handler.next(options);
},
onResponse: (response, handler) {
return handler.next(response);
},
onError: (DioException e, handler) {
print('Error: ${e.message}');
return handler.next(e);
},
),
);
Real-world example: Like a security guard checking ID before entering a building.
Step 7: Handle Errors Properly
try {
final response = await dio.get('/users');
} on DioException catch (e) {
if (e.type == DioExceptionType.connectionTimeout) {
print('Connection Timeout');
} else if (e.type == DioExceptionType.badResponse) {
print('Server Error');
} else {
print('Unexpected Error');
}
}
User-visible symptoms if ignored:
App crashes
No error messages
Poor user experience
Step 8: Create a Service Class (Best Practice)
class ApiService {
final Dio _dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
Future<Response> getUsers() async {
return await _dio.get('/users');
}
}
This keeps your code clean and maintainable.
Step 9: Use API in UI (Example)
FutureBuilder(
future: fetchUsers(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
return Text('Data Loaded');
},
)
Advantages of Using Dio
Better error handling
Clean and scalable architecture
Supports interceptors and middleware
Easy debugging with logs
Timeout and retry support
Disadvantages of Using Dio
Slight learning curve for beginners
More setup compared to basic HTTP
Overkill for very small apps
Common Mistakes to Avoid
Not handling exceptions properly
Hardcoding API URLs everywhere
Ignoring interceptors
Blocking UI during API calls
Summary
Handling API calls in Flutter using Dio makes your application more structured, scalable, and production-ready. By setting up a centralized Dio instance, using interceptors for authentication and logging, and properly managing errors, you can significantly improve both developer experience and app performance. In real-world scenarios, this approach helps avoid crashes, ensures smooth data flow, and makes your code easier to maintain as your application grows.