Kodokon kodokon.com

Llamar a una API HTTP: decodificación y estados de la pantalla

Estructura una llamada de red profesional con el paquete http: timeout, códigos de estado, decodificación tipada y estados exhaustivos.

11 min · 3 preguntas

Abrir esta lección en Kodokon

El 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.

BASH
flutter pub add http
DART
import '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>),
  ];
}
Timeout, estado comprobado, decodificación UTF-8 y luego tipado.

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.

DART
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]),
        ),
      ),
  };
}
Un switch exhaustivo: no se puede olvidar ningún estado.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. Tu llamada http.get recibe una respuesta 500. ¿Qué hace el paquete http?
    • Lanza una excepción que puedes capturar con try/catch.
    • Devuelve la Response con normalidad: te toca a ti comprobar el statusCode.
    • Reintenta automáticamente la petición tres veces.
  2. Los caracteres acentuados se muestran incorrectamente en los datos recibidos. ¿Cuál es la causa más probable?
    • jsonDecode no gestiona UTF-8.
    • Flutter no puede mostrar caracteres acentuados sin un paquete dedicado.
    • El servidor omite el charset: response.body se decodifica entonces como latin-1; usa utf8.decode(response.bodyBytes).
  3. ¿Cuál es la ventaja decisiva de una clase sealed para modelar el estado de una pantalla?
    • El switch se vuelve exhaustivo: el compilador señala cualquier estado sin gestionar.
    • Hace que la pantalla se renderice más rápido.
    • Te permite modificar el estado sin reconstruir el widget.