Introduzione

Quasi ogni app moderna comunica con un backend tramite API REST. In Flutter possiamo usare il pacchetto ufficiale http, ma quando le esigenze crescono — interceptor, timeout, retry, annullamento — il pacchetto Dio diventa la scelta più produttiva.

In questo articolo vedremo come configurare Dio, organizzare un client riutilizzabile e gestire correttamente gli errori.

Installazione

Aggiungi la dipendenza al tuo pubspec.yaml:

dependencies:
  dio: ^5.7.0

Esegui poi flutter pub get.

Una prima richiesta

Il modo più semplice per iniziare:

import 'package:dio/dio.dart';

final dio = Dio();

Future<void> fetchPost() async {
  final response = await dio.get(
    'https://jsonplaceholder.typicode.com/posts/1',
  );
  print(response.data); // Map già decodificata dal JSON
}

Una differenza importante rispetto al pacchetto http: Dio decodifica automaticamente il JSON, restituendo direttamente una Map o una List.

Configurare un client riutilizzabile

È buona pratica creare una singola istanza di Dio configurata con la base URL e i timeout:

Dio createDio() {
  return Dio(
    BaseOptions(
      baseUrl: 'https://api.example.com/v1/',
      connectTimeout: const Duration(seconds: 10),
      receiveTimeout: const Duration(seconds: 10),
      headers: {
        'Accept': 'application/json',
      },
    ),
  );
}

In questo modo le chiamate diventano più sintetiche:

final response = await dio.get('users/42');

Gli interceptor

Gli interceptor sono il punto di forza di Dio: permettono di intervenire prima della richiesta, dopo la risposta o in caso di errore. Un caso classico è aggiungere il token di autenticazione:

dio.interceptors.add(
  InterceptorsWrapper(
    onRequest: (options, handler) {
      final token = readTokenFromStorage();
      if (token != null) {
        options.headers['Authorization'] = 'Bearer $token';
      }
      return handler.next(options);
    },
    onError: (error, handler) {
      if (error.response?.statusCode == 401) {
        // qui potremmo tentare un refresh del token
      }
      return handler.next(error);
    },
  ),
);

Per il debug è comodo il LogInterceptor integrato:

dio.interceptors.add(LogInterceptor(responseBody: true));

Gestione degli errori

Dio lancia un'eccezione di tipo DioException quando qualcosa va storto. Possiamo distinguere i vari casi tramite il campo type:

Future<User?> getUser(String id) async {
  try {
    final response = await dio.get('users/$id');
    return User.fromJson(response.data);
  } on DioException catch (e) {
    switch (e.type) {
      case DioExceptionType.connectionTimeout:
      case DioExceptionType.receiveTimeout:
        throw Exception('Timeout di rete, riprova.');
      case DioExceptionType.badResponse:
        final code = e.response?.statusCode;
        throw Exception('Errore del server: $code');
      case DioExceptionType.connectionError:
        throw Exception('Nessuna connessione.');
      default:
        throw Exception('Errore imprevisto: ${e.message}');
    }
  }
}

Annullare una richiesta

Quando l'utente lascia una pagina mentre una chiamata è in corso, è utile annullarla per risparmiare risorse. Si usa un CancelToken:

final cancelToken = CancelToken();

dio.get('search?q=flutter', cancelToken: cancelToken);

// Quando serve annullare, ad esempio in dispose():
cancelToken.cancel('Schermata chiusa');

Inviare dati con POST

L'invio di un corpo JSON è altrettanto immediato:

final response = await dio.post(
  'users',
  data: {
    'name': 'Mario',
    'email': 'mario@example.com',
  },
);

Per i caricamenti di file si usa FormData:

final formData = FormData.fromMap({
  'avatar': await MultipartFile.fromFile('/path/to/img.png'),
});
await dio.post('upload', data: formData);

Conclusione

Dio offre un'esperienza completa per la gestione delle chiamate di rete in Flutter: configurazione centralizzata, interceptor, gestione granulare degli errori e annullamento delle richieste. Per progetti di una certa dimensione, vale la pena costruire un piccolo livello di servizio che incapsuli l'istanza di Dio, separando la logica di rete dal resto dell'app. Il prossimo passo naturale è combinare Dio con un generatore come retrofit per definire le API in modo dichiarativo.