One of the first things that an app developer is going to consider is how to make network requests. Dart has a builtin HTTP package, however, there exists a more powerful package called Dio. Dio provides a robust and easy-to-use API for making HTTP requests, handling interceptors, and dealing with complex scenarios like file uploads and downloads.

So why Dio?

Just like how Axios is preferred over Fetch in JavaScript, Dio is preferred over the default HTTP package because of the several advantages it offers:
  • Interceptors: Dio allows you to intercept requests and responses, enabling you to log traffic, add authentication headers, or modify the request body before it's sent to the server.
  • Multipart Form Data: Whereas you can send multipart form data with the default HTTP package, I feel like Dio simplifies this considerably (see here).
  • Error Handling: Dio provides a comprehensive error handling mechanism, allowing you to gracefully handle network errors and provide meaningful feedback to users. That said, I do use Riverpod for state management, which also has similar capabilities.
  • Global Configuration: Dio allows you to set global configuration options for all requests, such as base URLs, timeouts, and request headers.

Making HTTP Requests with Dio

import 'package:dio/dio.dart'; Future<Response> fetchData() async { final dio = Dio(); final response = await dio.get(''); return response; }
You can of course use the other HTTP verbs to make requests.

Handling Interceptors

Interceptors provide a powerful way to intercept and modify requests and responses. Dio allows you to add multiple interceptors, each with its own logic.
Dio dio = Dio(); dio.interceptors.add( InterceptorsWrapper( onRequest: (RequestOptions options, RequestInterceptorHandler handler) { print('Request: ${options.uri}'); return; }, ), );
This code snippet logs the request URL to the console before sending the request.
A more realistic use case is adding authorisation to all network requests
dio.interceptors.add( InterceptorsWrapper( onRequest: (options, handler) async { if (!options.headers.containsKey('Authorization')) { String? jwt = await yourFunctionToGetAJwt(); if (jwt != null) { options.headers['Authorization'] = 'Bearer $jwt'; } } return; }, ), );

Mocking Dio Requests for Testing

You're going to want to test code that makes network requests, but of course, you should not be contacting external APIs in tests. I like doing this using http_mock_adapter and the DioInterceptor it provides.
# With Dart dart pub add --dev http_mock_adapter # With Flutter flutter pub add --dev http_mock_adapter
import 'package:dio/dio.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http_mock_adapter/http_mock_adapter.dart'; void main() { test('Fetch data', () async { final dio = Dio(); final dioInterceptor = DioInterceptor(dio: dio); dio.interceptors.add(dioInterceptor); const path = ''; dioInterceptor.onGet( path, (server) => server.reply(200, {'title': 'Foo', 'body': 'Bar'}), ); final response = await dio.get(path); expect(response.statusCode, 200); expect(['title'], 'Foo'); expect(['body'], 'Bar'); }); }