Estructura una llamada de red profesional con el paquete http: timeout, códigos de estado, decodificación tipada y estados exhaustivos.
Abrir esta lección en KodokonEl paquete http cubre lo esencial: peticiones, cabeceras, cuerpo. dio añade interceptores, cancelación y reintento, a costa de una dependencia más pesada: una buena opción cuando la app crece más allá de unos pocos endpoints. Sea cual sea el cliente, la estructura de una llamada de red seria no cambia: un timeout, una comprobación del código de estado HTTP, una decodificación tipada y tres estados de pantalla (carga, error, datos) gestionados de forma explícita.
flutter pub add httpimport 'dart:convert';
import 'package:http/http.dart' as http;
class Article {
const Article({required this.title});
factory Article.fromJson(Map<String, dynamic> json) =>
Article(title: json['title'] as String);
final String title;
}
Future<List<Article>> fetchArticles() async {
final uri = Uri.https('api.example.com', '/articles');
final response = await http
.get(uri)
.timeout(const Duration(seconds: 8));
if (response.statusCode != 200) {
throw Exception('HTTP ${response.statusCode}');
}
final body = utf8.decode(response.bodyBytes);
final data = jsonDecode(body) as List<dynamic>;
return [
for (final item in data)
Article.fromJson(item as Map<String, dynamic>),
];
}Dos comportamientos suelen sorprender. Uno: http no lanza ninguna excepción con un estado 404 o 500; la petición "tuvo éxito" desde el punto de vista de la red, así que te toca a ti comprobar el statusCode. Dos: jsonDecode devuelve dynamic; haz el cast de inmediato (as List<dynamic>, as Map<String, dynamic>) para restaurar el tipado. Para payloads grandes, descarga la decodificación en un isolate con compute para no congelar la interfaz.
Queda representar el estado de la pantalla. Los flags booleanos como isLoading combinados con error != null siempre acaban en estados incoherentes (carga y error al mismo tiempo). Una clase sealed hace que los estados sean mutuamente excluyentes y que el switch sea exhaustivo: si mañana añades un estado, el compilador te señalará todas las pantallas que hay que actualizar.
import 'package:flutter/material.dart';
sealed class ArticlesState {
const ArticlesState();
}
class ArticlesLoading extends ArticlesState {
const ArticlesLoading();
}
class ArticlesError extends ArticlesState {
const ArticlesError(this.message);
final String message;
}
class ArticlesLoaded extends ArticlesState {
const ArticlesLoaded(this.titles);
final List<String> titles;
}
Widget buildArticlesBody(ArticlesState state) {
return switch (state) {
ArticlesLoading() => const Center(
child: CircularProgressIndicator(),
),
ArticlesError(:final message) => Center(
child: Text(message),
),
ArticlesLoaded(:final titles) => ListView.builder(
itemCount: titles.length,
itemBuilder: (context, index) => ListTile(
title: Text(titles[index]),
),
),
};
}http.get recibe una respuesta 500. ¿Qué hace el paquete http?try/catch.Response con normalidad: te toca a ti comprobar el statusCode.jsonDecode no gestiona UTF-8.response.body se decodifica entonces como latin-1; usa utf8.decode(response.bodyBytes).sealed para modelar el estado de una pantalla?switch se vuelve exhaustivo: el compilador señala cualquier estado sin gestionar.