# Deep Dive into Popular Flutter Packages: Dio

![Deep Dive into Popular Flutter Packages: Dio](https://miro.medium.com/v2/resize:fit:700/1*FbWgtfuB3vHBSb0vcjLMjA.jpeg)

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](https://pub.dev/packages/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:

```plaintext
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.

```plaintext
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_it` to ensure that only one instance of Dio is created and shared across the app, promoting efficiency and consistency in API handling.
- **BaseOptions**: The `BaseOptions` class 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.

```plaintext
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`:

```plaintext
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 `getUsers` method sends a GET request to the `/users` endpoint 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 `DioError` to 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:

```plaintext
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 `createUser` function uses `POST` to add a new user to the API. It takes a `userData` map, which contains the necessary fields for creating a user.
- **Update User**: The `updateUser` function sends a `PUT` request to update an existing user's information. This can be used to modify user details like name, email, or other attributes.
- **Delete User**: The `deleteUser` function deletes a user using a `DELETE` request, identified by their `userId`. This function ensures that user data is removed from the server.
- **Error Handling**: Each function includes error handling using `DioError` to 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:

```plaintext
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 `_fetchUsers` in the `initState` method 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 using `ListView.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.

---
Originally published on [Medium](https://ms3byoussef.medium.com/deep-dive-into-popular-flutter-packages-dio-09e9aea86df8) on November 22, 2024.
