Saltar al contenido principal

Obtener datos de internet

Refactoriza tu aplicación para usar un cliente de API robusto y listo para producción para la CLI de Wikipedia.

En el Capítulo 3, usaste el paquete http para hacer una solicitud simple. Ahora, volverás a visitar HTTP para construir un cliente de API robusto y listo para producción dentro del paquete wikipedia. Aprenderás a manejar parámetros de consulta (query parameters), mapear respuestas JSON a tus modelos de datos y exportar estas funciones para usarlas en tu CLI.

Qué lograrás

Construir URIs complejas con parámetros de consulta (query parameters)
Crear un cliente de API HTTP listo para producción
Exportar funciones y tipos para su reutilización

Prerrequisitos

#

Antes de comenzar este capítulo, asegúrate de:

  • Haber completado el Capítulo 10 y tener un entorno de desarrollo de Dart en funcionamiento con el proyecto dartpedia.
  • Entender conceptos básicos de redes (como APIs y solicitudes HTTP).
  • Entender formatos básicos de serialización de datos como JSON.

Tareas

#

En este capítulo, irás más allá de los simples scripts e implementarás una capa de API adecuada. Trabajarás dentro del paquete wikipedia para implementar la lógica del cliente de API, lo que mejora la escalabilidad y mantenibilidad de tu aplicación.

Tarea 1: Agregar la dependencia http al paquete wikipedia

#

Para realizar solicitudes HTTP, necesitas agregar el paquete http como una dependencia al paquete wikipedia.

  1. Abre el archivo wikipedia/pubspec.yaml dentro de tu proyecto.

  2. Localiza la sección dependencies.

  3. Agrega http: ^1.3.0 (o la última versión estable) bajo dependencies.

    yaml
    dependencies:
      http: ^1.3.0
    
  4. Guarda el archivo pubspec.yaml.

  5. Ejecuta dart pub get en tu terminal desde el directorio wikipedia.

Tarea 2: Implementar las llamadas a la API de Wikipedia

#

A continuación, crearás las funciones de la API para obtener datos de Wikipedia. Crearás tres archivos:

  • summary.dart: Este archivo contendrá funciones para recuperar resúmenes de artículos.
  • search.dart: Este archivo manejará las consultas de búsqueda para encontrar artículos.
  • get_article.dart: Este archivo contendrá funciones para obtener el contenido completo de un artículo.
  1. Crea el directorio wikipedia/lib/src/api.

  2. Crea el archivo wikipedia/lib/src/api/summary.dart.

  3. Agrega el siguiente código a wikipedia/lib/src/api/summary.dart:

    dart
    import 'dart:convert';
    import 'dart:io';
    
    import 'package:http/http.dart' as http;
    
    import '../model/summary.dart';
    
    Future<Summary> getRandomArticleSummary() async {
      final http.Client client = http.Client();
      try {
        final Uri url = Uri.https(
          'en.wikipedia.org',
          '/api/rest_v1/page/random/summary',
        );
        final http.Response response = await client.get(url);
        if (response.statusCode == 200) {
          final Map<String, Object?> jsonData =
              jsonDecode(response.body) as Map<String, Object?>;
          return Summary.fromJson(jsonData);
        } else {
          throw HttpException(
            '[WikipediaApiClient.getRandomArticleSummary] '
            'statusCode=${response.statusCode}, body=${response.body}',
          );
        }
      } on FormatException {
        // todo: log exceptions
        rethrow;
      } finally {
        client.close();
      }
    }
    
    Future<Summary> getArticleSummaryByTitle(String articleTitle) async {
      final http.Client client = http.Client();
      try {
        final Uri url = Uri.https(
          'en.wikipedia.org',
          '/api/rest_v1/page/summary/$articleTitle',
        );
        final http.Response response = await client.get(url);
        if (response.statusCode == 200) {
          final Map<String, Object?> jsonData =
              jsonDecode(response.body) as Map<String, Object?>;
          return Summary.fromJson(jsonData);
        } else {
          throw HttpException(
            '[WikipediaApiClient.getArticleSummaryByTitle] '
            'statusCode=${response.statusCode}, body=${response.body}',
          );
        }
      } on FormatException {
        // todo: log exceptions
        rethrow;
      } finally {
        client.close();
      }
    }
    

    Este código define dos funciones: getRandomArticleSummary y getArticleSummaryByTitle. Ambas funciones usan el paquete http para realizar solicitudes GET a la API de Wikipedia y devuelven un objeto Summary. getRandomArticleSummary obtiene un resumen de un artículo aleatorio, mientras que getArticleSummaryByTitle obtiene un resumen para el título de un artículo específico.

  4. A continuación, crea el archivo wikipedia/lib/src/api/search.dart.

  5. Agrega el siguiente código a wikipedia/lib/src/api/search.dart:

    dart
    
    import 'dart:convert';
    import 'dart:io';
    
    import 'package:http/http.dart' as http;
    
    import '../model/search_results.dart';
    
    Future<SearchResults> search(String searchTerm) async {
      final http.Client client = http.Client();
      try {
        final Uri url = Uri.https(
          'en.wikipedia.org',
          '/w/api.php',
          <String, Object?>{
            'action': 'opensearch',
            'format': 'json',
            'search': searchTerm,
          },
        );
        final http.Response response = await client.get(url);
        if (response.statusCode == 200) {
          final List<Object?> jsonData = jsonDecode(response.body) as List<Object?>;
          return SearchResults.fromJson(jsonData);
        } else {
          throw HttpException(
            '[WikipediaApiClient.search] '
            'statusCode=${response.statusCode}, '
            'body=${response.body}',
          );
        }
      } on FormatException {
        rethrow;
      } finally {
        client.close();
      }
    }
    

    Este código define la función search, que utiliza el paquete http para realizar una solicitud GET al endpoint opensearch de la API de Wikipedia y devuelve un objeto SearchResults. El endpoint opensearch se utiliza para buscar artículos de Wikipedia basados en un término de búsqueda.

  6. Crea el archivo wikipedia/lib/src/api/get_article.dart.

  7. Agrega el siguiente código a wikipedia/lib/src/api/get_article.dart:

    dart
    import 'dart:convert';
    import 'dart:io';
    
    import 'package:http/http.dart' as http;
    
    import '../model/article.dart';
    
    Future<List<Article>> getArticleByTitle(String title) async {
      final http.Client client = http.Client();
      try {
        final Uri url = Uri.https(
          'en.wikipedia.org',
          '/w/api.php',
          <String, Object?>{
            // order matters - explaintext must come after prop
            'action': 'query',
            'format': 'json',
            'titles': title.trim(),
            'prop': 'extracts',
            'explaintext': '',
          },
        );
        final http.Response response = await client.get(url);
        if (response.statusCode == 200) {
          final Map<String, Object?> jsonData =
              jsonDecode(response.body) as Map<String, Object?>;
          return Article.listFromJson(jsonData);
        } else {
          throw HttpException(
            '[WikipediaApiClient.getArticleByTitle] '
            'statusCode=${response.statusCode}, '
            'body=${response.body}',
          );
        }
      } on FormatException {
        // TODO: log
        rethrow;
      } finally {
        client.close();
      }
    }
    

    Este código define la función getArticleByTitle, que utiliza el paquete http para realizar una solicitud GET a la API de Wikipedia y devuelve un objeto List<Article>. Esta función recupera el contenido de un artículo de Wikipedia basándose en su título.

Tarea 3: Exportar las funciones de la API

#

Ahora que has creado las funciones de la API, necesitas exportarlas desde la librería wikipedia para que puedan ser utilizadas por el paquete cli. También exportarás los modelos existentes.

  1. Abre el archivo wikipedia/lib/wikipedia.dart.

  2. Agrega las siguientes sentencias export al archivo:

    dart
    export 'src/api/get_article.dart';
    export 'src/api/search.dart';
    export 'src/api/summary.dart';
    export 'src/model/article.dart';
    export 'src/model/search_results.dart';
    export 'src/model/summary.dart';
    export 'src/model/title_set.dart';
    

    Estas sentencias export hacen que las funciones y modelos de la API estén disponibles para otros paquetes que dependan del paquete wikipedia.

Tarea 4: Verificar con pruebas

#

Ahora que has implementado las funciones de la API y actualizado las dependencias del paquete, es una buena práctica ejecutar las pruebas que creaste en el capítulo anterior. Esto confirmará que tus cambios no han roto la funcionalidad existente del paquete wikipedia.

  1. Abre tu terminal y navega al directorio wikipedia/test.

  2. Elimina el archivo de prueba predeterminado ejecutando el comando rm wikipedia_test.dart (en macOS o Linux) o del wikipedia_test.dart (en Windows). Este archivo se generó automáticamente pero no se utiliza en nuestro proyecto.

  3. Abre tu terminal y navega al directorio wikipedia.

  4. Ejecuta el comando dart test.

    Deberías ver una salida similar a esta, confirmando que todas tus pruebas existentes aún pasan:

    bash
    00:02 +3: All tests passed!
    This confirms that the wikipedia package is still working as expected.
    

Revisión

#

Qué lograste

Aquí tienes un resumen de lo que construiste y aprendiste en esta lección.
Construiste URIs complejas con parámetros de consulta (query parameters)

Usaste Uri.https() con un mapa de parámetros de consulta para construir URLs de API. Este enfoque codifica adecuadamente los caracteres especiales y maneja combinaciones complejas de parámetros de consulta.

Creaste un cliente de API listo para producción

Implementaste una variedad de funciones que gestionan adecuadamente el ciclo de vida del cliente HTTP con try y finally, y mapean las respuestas JSON crudas a objetos Dart tipados.

Exported functions and types for reuse

Añadiste sentencias de exportación en wikipedia.dart para exponer tanto las funciones de la API como los tipos de modelos de datos. Esto crea una librería pública que tu cli paquete puede importar y usar directamente.

Cuestionario

#

Pon a prueba tu comprensión

1 / 3
How do you construct a URL with query parameters using Uri.https?
  1. Llamar a .addQueryParam('q', 'dart') en el objeto Uri.

    No exactamente.

    Los objetos Uri son inmutables y no tienen un método addQueryParam. Los parámetros de consulta deben especificarse de manera diferente.

  2. Usar concatenación de cadenas: Uri.https('api.com', '/search') + '?q=dart'

    No exactamente.

    No puedes concatenar cadenas a objetos Uri usando +. Uri proporciona un mecanismo diferente para agregar parámetros de consulta.

  3. Pasarlos como un mapa en el tercer argumento: Uri.https('api.com', '/search', {'q': 'dart'})

    ¡Correcto!

    El tercer parámetro acepta un Map<String, dynamic> de parámetros de consulta. Dart maneja la codificación URL automáticamente.

  4. Añadirlos a la cadena de la ruta: Uri.https('api.com', '/search?q=dart')

    No exactamente.

    Los parámetros de consulta (query parameters) en la cadena de la ruta funcionan, pero no son el enfoque más limpio.

After calling client.get(url), how should you check if the request was successful?
  1. Envolver la llamada en try/catch, ya que las solicitudes exitosas no lanzan excepciones.

    No exactamente.

    Los errores HTTP (como 404 y 500) no lanzan excepciones de forma predeterminada. La solicitud se completa normalmente incluso cuando el servidor devuelve un error.

  2. Verificar si response.body no está vacío.

    No exactamente.

    Un cuerpo vacío podría ser válido (como un 204 No Content). El contenido del cuerpo por sí solo no te indica si la solicitud tuvo éxito.

  3. Verificar si response.statusCode == 200 (u otro código de éxito).

    ¡Correcto!

    Los códigos de estado HTTP indican éxito (200-299) o varios fallos (400s, 500s). Siempre verifícalos antes de procesar el cuerpo de la respuesta.

  4. Verificar si response.isSuccess devuelve true.

    No exactamente.

    No hay una propiedad isSuccess en Response. Necesitas examinar una propiedad diferente del objeto de respuesta.

The lesson's API functions use a finally block to call client.close(). Why is this important?
  1. Para enviar un mensaje de 'conexión cerrada' al servidor.

    No exactamente.

    Aunque cerrar afecta a la conexión, el propósito principal es la limpieza del lado del cliente, not comunicarse con el servidor.

  2. Para guardar los datos de la respuesta en el disco antes de que la función retorne.

    No exactamente.

    close() no guarda datos en ningún lado. Guardar datos requeriría operaciones explícitas sobre archivos.

  3. Para garantizar que los recursos de red se liberen incluso si ocurre una excepción.

    ¡Correcto!

    finally se ejecuta tanto si el bloque try tiene éxito como si lanza una excepción. Esto asegura que las conexiones del cliente se cierren adecuadamente, evitando fugas de recursos.

  4. Para limpiar la respuesta de la memoria y activar el recolector de basura (garbage collector).

    No exactamente.

    El recolector de basura de Dart gestiona la memoria automáticamente. close() libera un tipo diferente de recurso.

Siguiente lección

#

En la siguiente lección, completarás la CLI integrando el paquete wikipedia con el paquete cli. Implementarás la lógica de comandos y mostrarás los resultados al usuario.