Conectar tu app a una API significa pedirle datos a un servidor en internet y mostrarlos dentro de tu aplicación en tiempo real.
¿Sabías que la mayoría de las apps que usas todos los días — Rappi, Mercado Libre, OXXO Pay — no guardan sus datos en el teléfono? Los obtienen desde servidores remotos cada vez que los necesitan. Eso es exactamente lo que aprenderás a hacer en esta lección.
Cómo funciona una petición HTTP
Cuando tu app quiere datos de internet, hace una petición HTTP. Es como enviar un mensaje a un servidor que dice: "Oye, dame la lista de productos" o "Aquí están los datos del usuario, guárdalos."
El servidor responde con un archivo JSON. JSON es un formato de texto muy ordenado. Se parece a esto:
{
"nombre": "Carlos Méndez",
"saldo": 3500,
"ciudad": "Monterrey"
}
Tu app lee ese texto y lo convierte en objetos que puedes mostrar en pantalla. Así de simple es el ciclo.
El paquete que necesitas: http
En Flutter, el paquete más popular para hacer peticiones HTTP se llama, precisamente, http. Agrégalo en tu archivo pubspec.yaml:
dependencies:
http: ^1.2.0
Después corre flutter pub get en tu terminal. Listo, ya tienes todo lo necesario.
La historia de Andrés y la app de precios
Andrés trabaja como analista en una distribuidora de bebidas que surte a tienditas en Jalisco. Su jefe le pidió una app interna que mostrara los precios actualizados de los productos FEMSA directo desde su sistema.
El problema era claro: si Andrés guardaba los precios dentro de la app, tendría que actualizar la app cada vez que cambiara un precio. Eso era ineficiente.
Su solución fue conectar la app a la API interna de la empresa. Cada vez que el vendedor abre la app, los precios se obtienen del servidor. Si el jefe cambia un precio a las 8 de la mañana, el vendedor lo ve actualizado a las 8:01.
Así implementó Andrés una petición básica en Flutter:
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<List<dynamic>> obtenerProductos() async {
final url = Uri.parse('https://api.distribuidora.com.mx/productos');
final respuesta = await http.get(url);
if (respuesta.statusCode == 200) {
final datos = jsonDecode(respuesta.body);
return datos['productos'];
} else {
throw Exception('Error al cargar productos');
}
}
¿Ves el await? Igual que con el almacenamiento local, las peticiones de red son asíncronas. Si no usas await, tu app no espera la respuesta y muestra datos vacíos.
Mostrar los datos en pantalla con FutureBuilder
Obtener los datos es solo la mitad del trabajo. El otro paso es mostrarlos en la interfaz. Para eso, Flutter tiene un widget llamado FutureBuilder.
FutureBuilder espera a que tu función asíncrona termine y luego construye la interfaz con los datos recibidos. Así lo usó Andrés:
FutureBuilder<List<dynamic>>(
future: obtenerProductos(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else if (snapshot.hasError) {
return Text('Ocurrió un error. Verifica tu conexión.');
} else {
final productos = snapshot.data!;
return ListView.builder(
itemCount: productos.length,
itemBuilder: (context, index) {
final p = productos[index];
return ListTile(
title: Text(p['nombre']),
subtitle: Text('Precio: \$${p["precio"]}'),
);
},
);
}
},
)
Este código maneja tres estados: cargando, error y datos listos. Esa es la práctica profesional. Nunca muestres solo el caso feliz.
La historia de Mariana y la app de Liverpool
Mariana es desarrolladora freelance en Ciudad de México. Un cliente pequeño — una boutique que vende ropa de temporada — le pidió una app que mostrara su catálogo actualizado.
Ella decidió usar una API pública de prueba llamada DummyJSON para el prototipo. Así podía mostrarle al cliente cómo funcionaría la app antes de conectar el sistema real.
Lo primero que hizo Mariana fue crear un modelo de datos. En lugar de trabajar con Map<String, dynamic> (que es propenso a errores tipográficos), creó una clase:
class Producto {
final String nombre;
final double precio;
final String imagen;
Producto({
required this.nombre,
required this.precio,
required this.imagen,
});
factory Producto.fromJson(Map<String, dynamic> json) {
return Producto(
nombre: json['title'],
precio: (json['price'] as num).toDouble(),
imagen: json['thumbnail'],
);
}
}
Luego su función de carga lucía así:
Future<List<Producto>> cargarCatalogo() async {
final url = Uri.parse('https://dummyjson.com/products?limit=10');
final respuesta = await http.get(url);
if (respuesta.statusCode == 200) {
final datos = jsonDecode(respuesta.body);
final lista = datos['products'] as List;
return lista.map((item) => Producto.fromJson(item)).toList();
} else {
throw Exception('No se pudo cargar el catálogo');
}
}
El cliente quedó impresionado con el prototipo. Ver los datos reales en pantalla — aunque fueran de prueba — es mucho más convincente que un wireframe estático.
Enviar datos al servidor con POST
No solo vas a leer datos. A veces necesitas enviarlos. Por ejemplo: registrar un pedido, guardar el perfil de un usuario o enviar un formulario.
Para eso se usa el método POST. Imagina que en la app de Andrés el vendedor puede registrar un nuevo pedido:
Future<void> registrarPedido(String clienteId, int cantidad) async {
final url = Uri.parse('https://api.distribuidora.com.mx/pedidos');
final respuesta = await http.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'cliente_id': clienteId,
'cantidad': cantidad,
}),
);
if (respuesta.statusCode == 201) {
print('Pedido registrado correctamente');
} else {
throw Exception('Error al registrar pedido');
}
}
Nota el headers. Le dices al servidor que le estás mandando JSON, no un formulario. Si omites ese encabezado, muchos servidores no entenderán tu mensaje.
Errores comunes al consumir APIs
Aquí es donde muchos desarrolladores principiantes pierden tiempo. Conoce los errores más frecuentes para evitarlos:
Error 1: No manejar el estado de carga.
Si la app no muestra nada mientras espera la respuesta, el usuario pensará que se congeló. Siempre muestra un CircularProgressIndicator o un esqueleto de carga.
Error 2: No verificar el statusCode.
Un código 200 significa éxito. Un 404 significa que la ruta no existe. Un 500 es error del servidor. Si no verificas el código, tu app puede mostrar datos vacíos o crashear sin razón aparente.
Error 3: Llamar la API en cada rebuild.
Si colocas tu Future directo dentro del método build(), Flutter la llamará cada vez que la pantalla se reconstruya. Eso puede generar cientos de peticiones innecesarias. Guarda el Future en una variable dentro de initState().
Error 4: No manejar la falta de internet. En México, la conectividad puede fallar. Un usuario en el metro, en una zona rural de Oaxaca, o en un almacén con señal débil puede perder la conexión. Siempre muestra un mensaje claro: "Sin conexión. Intenta más tarde." Combina esto con la estrategia offline-first que aprendiste en la lección anterior.
Error 5: Exponer tu API key en el código. Si tu API requiere una llave de autenticación, nunca la escribas directamente en el código fuente. Cualquiera que descargue tu APK puede extraerla. Usa variables de entorno o un servidor intermedio (proxy) que maneje la autenticación.
Lo que aprendiste hoy
Hoy conectaste tu app al mundo real. Aprendiste a pedir datos, mostrarlos en pantalla y enviar información al servidor. Eso convierte tu app de un proyecto estático en una herramienta verdaderamente útil.
Andrés ahora tiene a su equipo de ventas actualizado en tiempo real. Mariana convenció a su cliente con un prototipo funcional. Tú puedes hacer lo mismo.
La próxima lección cubrirá cómo preparar tu app para publicarla: íconos, permisos, firma del APK y el proceso de subida a Google Play.