Una petición HTTP es un mensaje que una app envía a un servidor para pedir o enviar datos.
En el artículo anterior organizamos nuestro código creando un Service. Ahora vamos a rellenar ese servicio con lógica real.
Para que nuestra aplicación Flutter hable con un servidor (una API REST), necesitamos un cliente HTTP. Aunque Dart trae herramientas nativas como HttpClient, para empezar suele ser más cómodo usar el paquete http.
Es ligero, basado en Future (asíncrono) y muy fácil de usar. Vamos a ver cómo realizar las operaciones que usarás en el 90% de tus Apps.
Instalar el paquete http
Como vimos en los primeros módulos, las librerías externas se gestionan en el pubspec.yaml.
Abre la terminal en la raíz de tu proyecto y ejecuta:
flutter pub add http
Esto añadirá la última versión compatible a tu archivo de configuración. Ahora, en tu archivo de servicio (user_service.dart), impórtalo así:
import 'dart:convert'; // Para transformar datos a JSON
import 'package:http/http.dart' as http;
¿Por qué as http?
Es una buena práctica darle un alias. Así, cuando escribamos código, usaremos http.get(...) en lugar de get(...). Esto evita confusiones con otras funciones de tu código que puedan llamarse igual.
Petición GET para leer datos
El verbo GET se utiliza para pedir información al servidor. Es lo que hace tu navegador cuando entras en una web.
Para este ejemplo, usaremos JSONPlaceholder, una API pública gratuita y falsa para pruebas.
Future<void> obtenerUsuarios() async {
// 1. Definimos la URL (Endpoint)
// El paquete http recibe un objeto Uri
var url = Uri.parse('https://jsonplaceholder.typicode.com/users');
// 2. Hacemos la llamada (Esperamos con await)
var response = await http.get(url);
// 3. Verificamos si fue bien
if (response.statusCode == 200) {
// 200 OK: La petición fue un éxito
print('Respuesta del servidor: ${response.body}');
} else {
// 404, 500, etc: Algo salió mal
print('Error en la petición: ${response.statusCode}');
}
}
El objeto Uri
Fíjate en Uri.parse(...). La API del paquete http recibe un objeto Uri, que representa por separado el esquema, el dominio, la ruta y los parámetros. Uri.parse comprueba la sintaxis, pero también acepta URI relativas, así que no garantiza por sí solo que exista un dominio o que el servidor responda.
Petición POST para enviar datos
El verbo POST se usa para enviar información nueva al servidor (crear un usuario, publicar un comentario, iniciar sesión).
Aquí la cosa cambia un poco porque necesitamos enviar dos cosas extra:
- Headers (Cabeceras): Para decirle al servidor “Oye, te estoy enviando datos en formato JSON”.
- Body (Cuerpo): Los datos en sí, convertidos a String.
Future<void> crearUsuario() async {
var url = Uri.parse('https://jsonplaceholder.typicode.com/users');
// Hacemos el POST
var response = await http.post(
url,
// 1. Headers: Importante especificar el tipo de contenido
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
// 2. Body: Convertimos nuestro mapa de Dart a un String JSON
body: jsonEncode({
"name": "Luis Llamas",
"username": "luisllamas",
"email": "[email protected]"
}),
);
if (response.statusCode == 201) {
// 201 Created: El recurso se creó correctamente
print('Usuario creado con éxito: ${response.body}');
} else {
print('Error al crear: ${response.statusCode}');
}
}
Para usar jsonEncode, necesitas importar dart:convert. Esta función transforma un mapa de Dart en un texto JSON válido, por ejemplo {"clave":"valor"}, que es lo que viaja por la red.
Integrarlo en el servicio
Ahora vamos a volver a nuestro user_service.dart del artículo anterior y vamos a hacerlo funcionar de verdad.
import 'dart:convert';
import 'package:http/http.dart' as http;
class UserService {
final String _baseUrl = 'jsonplaceholder.typicode.com';
Future<List<dynamic>> getUsers() async {
// Construimos la URL de forma segura
final url = Uri.https(_baseUrl, '/users');
final response = await http.get(url);
if (response.statusCode == 200) {
// ✅ ÉXITO
// response.body es un STRING gigante.
// jsonDecode lo convierte a una Lista de Mapas (List<dynamic>)
String body = response.body;
List<dynamic> datosDecodificados = jsonDecode(body);
return datosDecodificados;
} else {
// ❌ ERROR
throw Exception('Fallo al cargar usuarios');
}
}
}
El problema del tipo dynamic
Si te fijas en el código de arriba, he devuelto List<dynamic>.
Esto significa que cuando uses estos datos en tu pantalla, tendrás que hacer cosas como: usuario['name'] o usuario['email'].
Text(usuarios[index]['nmae']) // 😱 Error tipográfico ('nmae'), y Dart no te avisa
Trabajar con dynamic es peligroso. Pierdes el autocompletado y si escribes mal el nombre de una propiedad, la App explotará en la cara del usuario.