Pular para o conteúdo principal

Busque dados da internet

Refatore seu aplicativo para usar um cliente de API robusto e pronto para produção no CLI da Wikipedia.

No Capítulo 3, você usou o pacote http para fazer uma requisição simples. Agora, você vai revisitar o HTTP para construir um cliente de API robusto e pronto para produção dentro do pacote wikipedia. Você vai aprender como lidar com parâmetros de consulta, mapear respostas JSON para seus modelos de dados e exportar essas funções para uso na sua CLI.

O que você vai realizar

Construir URIs complexas com parâmetros de consulta
Criar um cliente de API HTTP pronto para produção
Exportar funções e tipos para reutilização

Pré-requisitos

#

Antes de começar este capítulo, certifique-se de:

  • Ter concluído o Capítulo 11 e ter um ambiente de desenvolvimento Dart funcionando com o projeto dartpedia.
  • Entender conceitos básicos de redes (como APIs e requisições HTTP).
  • Entender formatos básicos de serialização de dados, como JSON.

Tarefas

#

Neste capítulo, você vai deixar de lado os scripts simples e implementar uma camada de API adequada. Você vai trabalhar dentro do pacote wikipedia para implementar a lógica do cliente de API, o que melhora a escalabilidade e a manutenibilidade do seu aplicativo.

Tarefa 1: Adicione a dependência http ao pacote wikipedia

#

Para fazer requisições HTTP, você precisa adicionar o pacote http como dependência do pacote wikipedia.

  1. Abra o arquivo wikipedia/pubspec.yaml no seu projeto.

  2. Localize a seção dependencies.

  3. Adicione http: ^1.3.0 (ou a versão estável mais recente) em dependencies.

    wikipedia/pubspec.yaml
    yaml
    dependencies:
      http: ^1.3.0
    
  4. Salve o arquivo pubspec.yaml.

  5. Execute dart pub get no terminal, a partir do diretório wikipedia.

Tarefa 2: Implemente as chamadas à API da Wikipedia

#

Em seguida, você vai criar as funções de API para buscar dados da Wikipedia. Você vai criar três arquivos:

  • summary.dart: este arquivo vai conter funções para recuperar resumos de artigos.
  • search.dart: este arquivo vai tratar as consultas de busca para encontrar artigos.
  • get_article.dart: este arquivo vai conter funções para buscar o conteúdo completo de um artigo.
  1. Crie o diretório wikipedia/lib/src/api.

  2. Crie o arquivo wikipedia/lib/src/api/summary.dart.

  3. Adicione o seguinte código em wikipedia/lib/src/api/summary.dart:

    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 duas funções: getRandomArticleSummary e getArticleSummaryByTitle. Ambas usam o pacote http para fazer requisições GET à API da Wikipedia e retornar um objeto Summary. getRandomArticleSummary busca o resumo de um artigo aleatório, enquanto getArticleSummaryByTitle busca o resumo de um título de artigo específico.

  4. Em seguida, crie o arquivo wikipedia/lib/src/api/search.dart.

  5. Adicione o seguinte código em wikipedia/lib/src/api/search.dart:

    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 a função search, que usa o pacote http para fazer uma requisição GET ao endpoint opensearch da API da Wikipedia e retorna um objeto SearchResults. O endpoint opensearch é usado para buscar artigos da Wikipedia com base em um termo de busca.

  6. Crie o arquivo wikipedia/lib/src/api/get_article.dart.

  7. Adicione o seguinte código em wikipedia/lib/src/api/get_article.dart:

    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 a função getArticleByTitle, que usa o pacote http para fazer uma requisição GET à API da Wikipedia e retorna um objeto List<Article>. Essa função recupera o conteúdo de um artigo da Wikipedia com base no seu título.

Tarefa 3: Exporte as funções de API

#

Agora que você criou as funções de API, precisa exportá-las da biblioteca wikipedia para que possam ser usadas pelo pacote cli. Você também vai exportar os modelos existentes.

  1. Abra o arquivo wikipedia/lib/wikipedia.dart.

  2. Adicione as seguintes declarações export ao arquivo:

    wikipedia/lib/wikipedia.dart
    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';
    

    Essas declarações export tornam as funções de API e os modelos disponíveis para outros pacotes que dependem do pacote wikipedia.

Tarefa 4: Verifique com testes

#

Agora que você implementou as funções de API e atualizou as dependências do pacote, é uma boa prática executar os testes que você criou no capítulo anterior. Isso confirma que suas mudanças não quebraram a funcionalidade existente do pacote wikipedia.

  1. Abra o terminal e navegue até o diretório wikipedia/test.

  2. Remova o arquivo de teste padrão executando o comando rm wikipedia_test.dart (no macOS ou Linux) ou del wikipedia_test.dart (no Windows). Esse arquivo foi gerado automaticamente, mas não é usado no nosso projeto.

  3. Abra o terminal e navegue até o diretório wikipedia.

  4. Execute o comando dart test.

    Você deve ver uma saída parecida com esta, confirmando que todos os seus testes existentes continuam passando:

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

Revisão

#

O que você realizou

Veja um resumo do que você construiu e aprendeu nesta lição.
Construiu URIs complexas com parâmetros de consulta

Você usou Uri.https() com um mapa de parâmetros de consulta para montar as URLs da API. Essa abordagem codifica corretamente os caracteres especiais e lida com combinações complexas de parâmetros.

Criou um cliente de API pronto para produção

Você implementou várias funções que gerenciam corretamente o ciclo de vida do cliente HTTP com try e finally, e mapeia as respostas JSON brutas para objetos Dart tipados.

Exportou funções e tipos para reutilização

Você adicionou declarações export em wikipedia.dart para expor tanto as funções de API quanto os tipos de modelo de dados. Isso cria uma biblioteca pública que o seu pacote cli pode importar e usar diretamente.

Quiz

#

Confira seu entendimento

1 / 3
Como você monta uma URL com parâmetros de consulta usando Uri.https?
  1. Passe-os como um terceiro argumento, um map: Uri.https('api.com', '/search', {'q': 'dart'})

    Isso mesmo!

    O terceiro parâmetro aceita um Map<String, dynamic> com os parâmetros de consulta. O Dart faz a codificação da URL automaticamente.

  2. Acrescente-os à string do caminho: Uri.https('api.com', '/search?q=dart')

    Não exatamente.

    Parâmetros de consulta na string do caminho funcionam, mas não são a abordagem mais limpa.

  3. Use concatenação de strings: Uri.https('api.com', '/search') + '?q=dart'

    Não exatamente.

    Não é possível concatenar strings a objetos Uri com +. Uri oferece outro mecanismo para adicionar parâmetros de consulta.

  4. Chame .addQueryParam('q', 'dart') no objeto Uri.

    Não exatamente.

    Objetos Uri são imutáveis e não têm um método addQueryParam. Os parâmetros de consulta devem ser especificados de outra forma.

Depois de chamar client.get(url), como você deve verificar se a requisição foi bem-sucedida?
  1. Verificar se response.statusCode == 200 (ou outro código de sucesso).

    Isso mesmo!

    Os códigos de status HTTP indicam sucesso (200-299) ou diversas falhas (400 e 500). Sempre verifique antes de processar o corpo da resposta.

  2. Verificar se response.body não está vazio.

    Não exatamente.

    Um corpo vazio pode ser válido (como um 204 No Content). O conteúdo do corpo, por si só, não diz se a requisição deu certo.

  3. Envolver a chamada em try/catch, já que requisições bem-sucedidas não lançam exceções.

    Não exatamente.

    Erros HTTP (como 404 e 500) não lançam exceções por padrão. A requisição termina normalmente mesmo quando o servidor retorna um erro.

  4. Verificar se response.isSuccess retorna true.

    Não exatamente.

    Não existe uma propriedade isSuccess em Response. Você precisa consultar outra propriedade do objeto de resposta.

As funções de API da lição usam um bloco finally para chamar client.close(). Por que isso é importante?
  1. Para garantir que os recursos de rede sejam liberados mesmo se ocorrer uma exceção.

    Isso mesmo!

    O finally roda tanto se o bloco try for bem-sucedido quanto se lançar uma exceção. Isso garante que as conexões do client sejam fechadas corretamente, evitando vazamento de recursos.

  2. Para salvar os dados da resposta em disco antes de a função retornar.

    Não exatamente.

    close() não salva dados em lugar nenhum. Salvar dados exigiria operações explícitas com arquivos.

  3. Para enviar uma mensagem de 'conexão fechada' ao servidor.

    Não exatamente.

    Embora fechar afete a conexão, o principal objetivo é a limpeza do lado do cliente, não a comunicação com o servidor.

  4. Para limpar a resposta da memória e acionar o garbage collector.

    Não exatamente.

    O garbage collector do Dart cuida da memória automaticamente. close() libera outro tipo de recurso.

Próxima lição

#

Na próxima lição, você vai concluir sua CLI integrando os comandos do pacote wikipedia com o pacote cli e adicionando logging para depuração e monitoramento de erros.

Encontrou essa página sem tradução ou que precisa de correção? Abra uma issue e nos ajude a manter esse site em PT-BR para ajudar pessoas como você.