Perché Retrofit in Flutter
Gestire manualmente le chiamate HTTP con Dio funziona bene, ma man mano che l'app cresce ci si ritrova a scrivere codice ripetitivo per costruire URL, serializzare parametri e deserializzare le risposte. Il pacchetto retrofit risolve questo problema generando automaticamente un client REST tipizzato a partire da un'interfaccia annotata, appoggiandosi a Dio per il trasporto e a json_serializable per la serializzazione.
Il risultato è un layer di rete dichiarativo, leggibile e a prova di refactoring: definisci cosa vuoi chiamare, non come.
Installazione
Aggiungi le dipendenze al pubspec.yaml:
dependencies:
dio: ^5.4.0
retrofit: ^4.1.0
json_annotation: ^4.9.0
dev_dependencies:
build_runner: ^2.4.8
retrofit_generator: ^8.1.0
json_serializable: ^6.7.1
Definire i modelli
Partiamo da un semplice modello serializzabile:
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
Creare l'interfaccia del client
Qui sta il cuore di Retrofit: si dichiara un'interfaccia annotata con @RestApi e si descrivono gli endpoint con annotazioni come @GET, @POST, @Path e @Body.
import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';
import 'user.dart';
part 'api_client.g.dart';
@RestApi(baseUrl: 'https://api.example.com')
abstract class ApiClient {
factory ApiClient(Dio dio, {String baseUrl}) = _ApiClient;
@GET('/users')
Future<List<User>> getUsers();
@GET('/users/{id}')
Future<User> getUser(@Path('id') int id);
@POST('/users')
Future<User> createUser(@Body() User user);
@GET('/users')
Future<List<User>> searchUsers(@Query('q') String query);
@DELETE('/users/{id}')
Future<void> deleteUser(@Path('id') int id);
}
Generare il codice
Una volta definite interfaccia e modelli, esegui il generatore:
dart run build_runner build --delete-conflicting-outputs
Questo crea i file api_client.g.dart e user.g.dart con tutta l'implementazione ripetitiva. Non dovrai mai scrivere a mano il parsing del JSON o la costruzione degli URL.
Usare il client
void main() async {
final dio = Dio();
final client = ApiClient(dio);
final users = await client.getUsers();
print('Utenti trovati: ${users.length}');
final user = await client.getUser(1);
print('Nome: ${user.name}');
final nuovo = await client.createUser(
User(id: 0, name: 'Mario', email: 'mario@example.com'),
);
print('Creato con id ${nuovo.id}');
}
Configurare Dio con interceptor
Uno dei vantaggi di appoggiarsi a Dio è poter riutilizzare tutta la sua potenza: interceptor per token di autenticazione, logging e gestione degli errori.
final dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 10),
));
dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
options.headers['Authorization'] = 'Bearer $token';
handler.next(options);
},
onError: (error, handler) {
if (error.response?.statusCode == 401) {
// logica di refresh del token
}
handler.next(error);
},
),
);
final client = ApiClient(dio);
Gestire header e form data
Retrofit supporta anche header dinamici e invio di form:
@POST('/login')
Future<TokenResponse> login(
@Field('username') String username,
@Field('password') String password,
);
@GET('/profile')
Future<User> profile(@Header('X-Api-Version') String version);
Annotare i parametri con @Field invia automaticamente i dati come application/x-www-form-urlencoded, mentre @Header permette di passare header specifici a livello di singola chiamata.
Best practice
- Separa il layer di rete: tieni le interfacce Retrofit in una cartella dedicata (
data/remote) e non esporle direttamente alla UI, ma passa attraverso un repository. - Gestisci le eccezioni: le
DioExceptionvanno intercettate e convertite in errori di dominio (ad esempio con un tipoResult) prima di raggiungere i widget. - Un client per dominio: se l'API è ampia, suddividi le chiamate in più interfacce (
AuthApi,UserApi,ProductApi) per mantenere il codice ordinato. - Rigenera dopo ogni modifica: ricordati di lanciare
build_runnerogni volta che modifichi le annotazioni o i modelli.
Conclusioni
Retrofit porta in Flutter un approccio dichiarativo e tipizzato alle chiamate REST, eliminando gran parte del codice boilerplate e riducendo gli errori. Combinato con Dio per il trasporto e json_serializable per la serializzazione, offre un layer di rete robusto e facilmente manutenibile, ideale per progetti di medie e grandi dimensioni.
