DEV Community

Cover image for Deep Dive into Popular Flutter Packages: Dio
مصعب يوسف
مصعب يوسف

Posted on Originally published at ms3byoussef.medium.com on

Deep Dive into Popular Flutter Packages: Dio

Deep Dive into Popular Flutter Packages: Dio

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
Enter fullscreen mode Exit fullscreen mode

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());
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

void main() {
  DioHandler.setup();
  runApp(MyApp());
}
Enter fullscreen mode Exit fullscreen mode

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 [];
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

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;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

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'),
                );
              },
            ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)