Si trabajas con Flutter y todavía usas el package HTTP puro, este artículo te va a ayudar: aprenderás —paso a paso y con código listo para copiar— todo lo que ofrece Flutter Dio, el cliente HTTP más potente del ecosistema Dart. Desde la instalación hasta interceptores, cancelación de peticiones, caché y gestión de errores. Coge un café y abre tu IDE.
Por qué apostar por Flutter Dio (y no por el package http
de siempre)
Flutter Dio se construye encima de HttpClient, pero añade una capa de azúcar sintáctico y funcionalidades “pro” que evitan reescribir boilerplate una y otra vez.
- Soporte nativo para GET, POST, PUT, PATCH, DELETE (y cualquier método personalizado).
- Interceptors: manipula peticiones y respuestas para logging, tokens de auth, refresh de expiraciones, etc.
- Multipart/form-data sin sudar tinta (subidas de imágenes o PDFs en dos líneas).
- Cancelación de peticiones (clave en scroll infinito o cuando el usuario cambia de pantalla).
- Timeouts y reintentos configurables.
- Configuración global de base URL, cabeceras y serialización.
En otras palabras: menos código repetido, más control y una API más expresiva gracias a Flutter Dio.
Instalación y setup básico
Añade la dependencia estable más reciente en tu pubspec.yaml (a junio 2025 la versión va por la 5.8.0+1):
dependencies:
flutter:
sdk: flutter
dio: ^5.8.0+1 # Flutter Dio
Ejecuta flutter pub get… ¡y listo para exprimir Flutter Dio!
Crear un cliente Flutter Dio con configuración global
late final Dio flutterDio;
void initFlutterDio() {
flutterDio = Dio(
BaseOptions(
baseUrl: 'https://jsonplaceholder.typicode.com',
connectTimeout: const Duration(seconds: 8),
receiveTimeout: const Duration(seconds: 8),
),
);
addInterceptors();
}
Modelo de datos para probar Flutter Dio
Trabajaremos con el endpoint público jsonplaceholder y este modelo sencillo:
class Post {
final int userId;
final int id;
final String title;
final String body;
Post({required this.userId, required this.id, required this.title, required this.body});
factory Post.fromJson(Map<String, dynamic> json) => Post(
userId: json['userId'] ?? 0,
id: json['id'] ?? 0,
title: json['title'] ?? '',
body: json['body'] ?? '',
);
}
Peticiones HTTP típicas
GET – listar recursos
Future<List<Post>> fetchPosts() async {
final response = await flutterDio.get('/posts');
return (response.data as List).map((json) => Post.fromJson(json)).toList();
}
POST – crear un recurso
Future<Post> createPost(String title, String body) async {
final response = await flutterDio.post(
'/posts',
data: {'title': title, 'body': body, 'userId': 1},
);
return Post.fromJson(response.data);
}
PUT – reemplazar un recurso
Future<Post> replacePost(int id) async {
final response = await flutterDio.put(
'/posts/$id',
data: {'title': 'Título actualizado', 'body': 'Body actualizado', 'userId': 1},
);
return Post.fromJson(response.data);
}
PATCH – actualizar parcialmente
Future<Post> patchPost(int id) async {
final response = await flutterDio.patch('/posts/$id', data: {'title': 'Solo el título'});
return Post.fromJson(response.data);
}
DELETE – eliminar
Future<void> deletePost(int id) async {
await flutterDio.delete('/posts/$id');
}
Cache sencilla en memoria
final Map<String, String> _cache = {};
Future<String> fetchWithCache(int id) async {
const keyPrefix = 'posts/';
final key = '$keyPrefix$id';
if (_cache.containsKey(key)) return 'CACHE:\n${_cache[key]}';
final response = await flutterDio.get('/posts/$id');
_cache[key] = response.data.toString();
return 'NETWORK:\n${response.data}';
}
Interceptors: el súper-poder
void addInterceptors() {
flutterDio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
options.headers['Authorization'] = 'Bearer <jwt>';
print('➡️ REQUEST[${options.method}] => ${options.uri}');
handler.next(options);
},
onResponse: (response, handler) {
print('✅ RESPONSE[${response.statusCode}]');
handler.next(response);
},
onError: (DioError e, handler) {
print('⛔️ ERROR[${e.response?.statusCode}] => ${e.message}');
handler.next(e);
},
),
);
}
Un solo interceptor en Flutter Dio centraliza logging, refresh de tokens y métricas.
Cancelar peticiones y ahorrar batería con Flutter Dio
final cancelToken = CancelToken();
Future<void> loadData() async {
try {
final response = await flutterDio.get('/posts', cancelToken: cancelToken);
// …
} on DioError catch (e) {
if (CancelToken.isCancel(e)) {
print('Petición cancelada');
}
}
}
void dispose() {
cancelToken.cancel(); // Cancela al cerrar la pantalla
}
Timeout, reintentos y manejo de errores elegante
Future<List<Post>> safeFetch() async {
try {
final rsp = await flutterDio.get('/posts').timeout(const Duration(seconds: 5));
return (rsp.data as List).map((e) => Post.fromJson(e)).toList();
} on TimeoutException {
throw Exception('Servidor lento, prueba más tarde');
} on DioError catch (e) {
throw Exception('Error [${e.type}] => ${e.message}');
}
}
Con Flutter Dio controlas cada escenario sin clutter de try-catch
redundante.
Ejemplo completo de UI mínima usando Flutter Dio
class DioDemo extends StatefulWidget {
const DioDemo({super.key});
@override
State<DioDemo> createState() => _DioDemoState();
}
class _DioDemoState extends State<DioDemo> {
@override
void initState() {
super.initState();
initFlutterDio();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter Dio Playground')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
ElevatedButton(onPressed: _handleGet, child: const Text('GET')),
ElevatedButton(onPressed: _handlePost, child: const Text('POST')),
ElevatedButton(onPressed: _handlePut, child: const Text('PUT')),
ElevatedButton(onPressed: _handlePatch, child: const Text('PATCH')),
ElevatedButton(onPressed: _handleDelete, child: const Text('DELETE')),
ElevatedButton(onPressed: _handleCache, child: const Text('GET + Cache')),
],
),
);
}
Future<void> _handleGet() async => print(await fetchPosts());
Future<void> _handlePost() async => print(await createPost('Nuevo', 'Body'));
Future<void> _handlePut() async => print(await replacePost(1));
Future<void> _handlePatch() async => print(await patchPost(1));
Future<void> _handleDelete() async => await deletePost(1);
Future<void> _handleCache() async => print(await fetchWithCache(1));
}
Compila, pulsa cada botón y observa en la consola cómo Flutter Dio firma las peticiones y respuestas.
Buenas prácticas rápidas con Flutter Dio
- Usa
BaseOptions
para no repetirbaseUrl
,headers
oqueryParameters
. - Mantén un solo cliente Flutter Dio por app o por dominio.
- Serializa JSON con
json_serializable
o Freezed. - En producción, nivela los logs; en debug, sácale el jugo a los interceptores de Flutter Dio.
- Guarda tokens en
flutter_secure_storage
, nunca en el código fuente.
Conclusiones
Implementar Flutter Dio añade robustez y flexibilidad al networking de tus apps Flutter. Interceptores, caché, cancelación y configuración global minimizan código repetido y preparan tu proyecto para miles de usuarios concurrentes. Pruébalo hoy, mide rendimiento y conviértelo en estándar.