Flutter is a powerful framework for building cross-platform mobile applications, and one of its strengths is the wide range of third-party packages available to streamline development. One such package is Dio, a powerful HTTP client for Dart that simplifies network communication. In this article, we will explore Dio and demonstrate how to use it to fetch a list of data from an API, implement a CRUD handler, and efficiently manage errors. We’ll also add the pretty_dio_logger package to help debug and visualize network requests during development.
What is Dio?
Dio is an HTTP client package that provides a flexible and powerful way to perform network requests in Dart and Flutter applications. It supports features such as:
- Interceptors: Allowing you to modify requests, responses, and handle errors.
- Global Configuration: Easy to set up global headers, timeout, and other settings.
- Canceling Requests: You can cancel any pending request.
- File Uploads and Downloads: It supports multipart file uploads and downloads.
- Error Handling: Built-in methods to handle HTTP errors more gracefully.
- Custom Request Transformation: Easily transform data before sending requests or after receiving responses.
Key Features of Dio
- Interceptors: Use interceptors to add logging, authentication, or request retries. Interceptors are especially useful for debugging, adding custom headers, or retrying failed requests.
- Error Handling: Dio provides flexible error handling, allowing you to handle different error types, such as network errors, timeout errors, and server-side errors. This helps create a more reliable and robust application.
- Request Cancellation: Dio supports canceling requests when they are no longer needed. This is particularly useful in cases like form submissions or search-as-you-type functionality, where outdated requests should be canceled to save resources.
- Custom Configuration: Dio allows developers to easily configure and extend the library, offering features like setting custom timeouts, configuring global headers, and adding custom interceptors.
- Asynchronous File Uploads: Dio makes it easy to upload files asynchronously with support for multipart uploads and progress tracking.
Setting Up Dio in a Flutter Project
To use Dio in a Flutter project, add the following dependencies to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
dio: ^4.0.0
pretty_dio_logger: ^1.4.0
get_it: ^7.2.0 # For lazy singleton pattern
Run flutter pub get to install the dependencies.
- dio: The core HTTP client package for Dart.
- pretty_dio_logger: A package used to add logging capabilities to Dio, which helps developers debug network requests effectively.
- get_it: A package used for dependency injection, allowing the setup of lazy singletons and managing instances throughout the app lifecycle.
Step 1: Setting Up Dio and Pretty Dio Logger
Let’s create a Dio instance configured with PrettyDioLogger to log network requests in debug mode.
Dio Handler with PrettyDioLogger
Create a new file called dio_handler.dart and set up Dio as a lazy singleton using the get_it package.
import 'package:dio/dio.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
import 'package:get_it/get_it.dart';
final getIt = GetIt.instance;
class DioHandler {
final Dio dio;
DioHandler._internal()
: dio = Dio(BaseOptions(
baseUrl: "https://example.com/api",
connectTimeout: 5000,
receiveTimeout: 3000,
)) {
dio.interceptors.add(PrettyDioLogger(
requestHeader: true,
requestBody: true,
responseHeader: false,
responseBody: true,
error: true,
compact: true,
));
}
static void setup() {
getIt.registerLazySingleton(() => DioHandler._internal());
}
}
Explanation
- PrettyDioLogger: The logger interceptor helps visualize requests, responses, and errors during debugging, making it easier to trace network issues. The logs include detailed information such as headers, request bodies, and response bodies.
-
Lazy Singleton: We use
get_itto ensure that only one instance of Dio is created and shared across the app, promoting efficiency and consistency in API handling. -
BaseOptions: The
BaseOptionsclass is used to configure the base URL, connection timeout, and receive timeout for our Dio client, ensuring consistent configuration for all API requests.
Run DioHandler.setup() in your main file to initialize Dio as a lazy singleton.
void main() {
DioHandler.setup();
runApp(MyApp());
}
Step 2: Handling GET Requests from an API
Next, we will create a simple API service to fetch a list of users from an API.
API Service
Create a new file called api_service.dart:
import 'package:dio/dio.dart';
import 'dio_handler.dart';
class ApiService {
final Dio _dio = DioHandler().dio;
Future<List<dynamic>> getUsers() async {
try {
final response = await _dio.get('/users');
return response.data;
} catch (e) {
if (e is DioError) {
print('Error occurred: ${e.message}');
}
return [];
}
}
}
Explanation
-
GET Request: The
getUsersmethod sends a GET request to the/usersendpoint and returns the response data as a list of users. This can be used to fetch user data from a REST API. -
Error Handling: We use
DioErrorto catch and print errors for better debugging. If an error occurs (such as a network error or server error), an empty list is returned to prevent the app from crashing.
Step 3: Implementing CRUD Operations
Let’s add more CRUD operations (Create, Update, Delete) to our API service.
Extended API Service with CRUD
Update api_service.dart to add more functions:
import 'package:dio/dio.dart';
import 'dio_handler.dart';
class ApiService {
final Dio _dio = DioHandler().dio;
Future<List<dynamic>> getUsers() async {
try {
final response = await _dio.get('/users');
return response.data;
} catch (e) {
if (e is DioError) {
print('Error occurred: ${e.message}');
}
return [];
}
}
Future<bool> createUser(Map<String, dynamic> userData) async {
try {
await _dio.post('/users', data: userData);
return true;
} catch (e) {
if (e is DioError) {
print('Error occurred: ${e.message}');
}
return false;
}
}
Future<bool> updateUser(int userId, Map<String, dynamic> userData) async {
try {
await _dio.put('/users/$userId', data: userData);
return true;
} catch (e) {
if (e is DioError) {
print('Error occurred: ${e.message}');
}
return false;
}
}
Future<bool> deleteUser(int userId) async {
try {
await _dio.delete('/users/$userId');
return true;
} catch (e) {
if (e is DioError) {
print('Error occurred: ${e.message}');
}
return false;
}
}
}
Explanation
-
Create User: The
createUserfunction usesPOSTto add a new user to the API. It takes auserDatamap, which contains the necessary fields for creating a user. -
Update User: The
updateUserfunction sends aPUTrequest to update an existing user's information. This can be used to modify user details like name, email, or other attributes. -
Delete User: The
deleteUserfunction deletes a user using aDELETErequest, identified by theiruserId. This function ensures that user data is removed from the server. -
Error Handling: Each function includes error handling using
DioErrorto catch and print relevant error messages, making it easier to diagnose issues when a network request fails.
Step 4: Using the API Service in a Flutter Widget
Now that we have our Dio handler and API service, let’s create a simple Flutter widget that fetches and displays a list of users.
User List Screen
Create a user_list_screen.dart file:
import 'package:flutter/material.dart';
import 'api_service.dart';
class UserListScreen extends StatefulWidget {
@override
_UserListScreenState createState() => _UserListScreenState();
}
class _UserListScreenState extends State<UserListScreen> {
final ApiService _apiService = ApiService();
List<dynamic> _users = [];
@override
void initState() {
super.initState();
_fetchUsers();
}
void _fetchUsers() async {
final users = await _apiService.getUsers();
setState(() {
_users = users;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('User List')),
body: _users.isEmpty
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: _users.length,
itemBuilder: (context, index) {
final user = _users[index];
return ListTile(
title: Text(user['name'] ?? 'No Name'),
subtitle: Text(user['email'] ?? 'No Email'),
);
},
),
);
}
}
Explanation
-
initState: We call
_fetchUsersin theinitStatemethod to load the user list when the widget is first created. This ensures that the data is fetched as soon as the screen is rendered. -
Loading State: While the data is loading, we show a
CircularProgressIndicator. Once the users are fetched, we display them in a list usingListView.builder, which provides an efficient way to display large lists of data.
Conclusion
In this article, we explored the Dio package for Flutter, which is a powerful and flexible HTTP client for making network requests. We used Dio to create a GET request to fetch a list of users from an API and extended the service to handle full CRUD operations. Additionally, we implemented PrettyDioLogger to help visualize network requests during development, and we used the lazy singleton pattern with get_it to ensure consistent access to a single instance of Dio throughout the application.
By using Dio, you can effectively manage API requests, implement robust error handling, and simplify debugging. With features like interceptors, request cancellation, and file uploads, Dio provides a comprehensive solution for working with REST APIs in Flutter. Give Dio a try in your next Flutter project and experience how it can make working with network requests more efficient, maintainable, and enjoyable.

Top comments (0)