Usando Codables em Swift 5 para consumir uma API de filmes

Photo by Erik Witsoe on Unsplash

Aposto que se você chegou até este artigo você é um desenvolvedor iOS, um cinéfilo ou ambos. Então me parece bem conveniente juntar essas duas coisas maravilhosas, que são filmes e programação, para aprender a usar um dos recursos mais legais do Swift: os Codables.

Acho válido começar mostrando como era feita a decodificação de um JSON até o Swift 3, então vamos levar em conta esse exemplo com um JSON simples, contendo algumas informações sobre um filme:

Poderíamos muito bem ter um Model para esse filme, e deixá-lo assim:

Agora, para conseguir pegar os dados do JSON e usá-los em nossa instância, precisávamos fazer isso:

Era bem funcional, porém, convenhamos que deixava o código extremamente grande e poluído, além de não ser uma boa ideia utilizar tantas Strings literais assim…

Com o Swift 4, nos deparamos com a inclusão dos protocolos Decodable, Encodable e Codable. Mas… Para que servem esses protocolos?

Com o protocolo Decodable nós podemos construir uma struct ou uma class que pode ser instanciada através da decodificação de um JSON.

O protocolo Encodable nos permite codificar nossas classes e structs para o formato JSON, facilitando muito em requisições POST, por exemplo.

Já o protocolo Codable é o melhor dos dois mundos! Ele abrange os protocolos Decodable e Encodable dentro dele (Decodable & Encodable).

Super legal, né?! Mas qual a diferença ao se utilizar esses protocolos? Vou te mostrar e, para começar, vamos ajustar nosso Model adicionando o protocolo Codable nele:

Note que eu mudei de var para let, não é necessário, mas como não iremos mais precisar alterar esses valores, preferi deixar assim. Outra mudança que optei em fazer, foi remover os tipos Optional das propriedades, já que agora não vamos ter mais que instanciar o objeto “na mão”.

Com isso feito, só precisamos agora decodificar nosso JSON, dessa maneira:

Olha que maravilha! Você só precisa passar qual struct/classe o nosso JSONDecoder deve utilizar para fazer a decodificação e voilà.

Simples, não? Note que os nomes das propriedades da nossa struct devem ser exatamente iguais às chaves do JSON e que os tipos das propriedades devem também bater com os do JSON. Caso o JSON possua chaves que comecem com letras maiúsculas, existe uma forma de tratar isso e veremos mais a frente.

Obs.: Nesse tutorial só vamos consumir uma API, sem enviar nada para ela, então poderíamos usar apenas o protocolo Decodable, mas usaremos o Codable por ser o mais comum e completo (quem sabe não saia uma parte II depois…). Mas enfim, chega de enrolação e bora codar!

A primeira coisa que você vai precisar fazer é solicitar uma API KEY gratuita da OMDb API (não precisa de aprovação, você recebe a chave na hora 😁), que será a API utilizada neste tutorial. Basta colocar seu email, selecionar se quer ajudá-los com algum valor ou se quer de forma gratuita (FREE) e clicar em Submit. Logo após isso, você receberá um email com sua chave e com um link para ativá-la, basta clicar nesse link e guardar sua chave. Pronto!

Agora, para a parte de código, eu criei este repositório no GitHub com o projeto inicial, só com as coisas essenciais para a construção do app e que não tem relação com Codables e APIs (como Storyboard e elementos de UI). Dessa forma podemos focar somente no necessário. Esse “esqueleto” do projeto está na branch bare, que está configurada como a padrão do repositório, então basta cloná-lo.

Observação importante: Ao abrir o projeto baixado, verá que o arquivo APIKey.swift estará em vermelho, pois ele não foi adicionado ao GitHub, por conter minha chave da API. Você pode apagar esse arquivo e criar um novo, com qualquer nome, e adicionar nele sua API KEY, dessa forma: let API_KEY = "SUA_CHAVE".

Outra observação é que o projeto visa mostrar o uso de Codables, e não adotar tão à risca regras de Design Patterns, por isso tanto o TableViewDelegate quanto o TableViewDataSource são extensions da nossa ViewController principal e encontram-se na pasta Delegates and DataSources.

Ao final do artigo, teremos um app assim:

Star Wars, Star Wars: Episode I The Phantom Menace; Star Wars: Episode II Attack of the Clones; Star Wars: Episode III Revenge of the Sith; Star Wars: Episode IV A New Hope; Star Wars: Episode V The Empire Strikes Back; Star Wars: Episode VI Return of the Jedi; Star Wars: Episode VII The Force Awakens; Star Wars: Episode IX The Rise of Skywalker; the Star Wars Expanded Universe; and all logos, characters, artwork, stories, information, names, and other elements associated thereto, are the sole and exclusive property of Lucasfilm Limited.

Pronto, agora que você possui tudo necessário para começar, vamos iniciar analisando o JSON de retorno da API utilizada.

Existem dois tipos principais de uso dessa API, o primeiro deles é para buscar filmes dado um título e, para isso, usamos o seguinte endpoint: https://www.omdbapi.com/?apikey={API_KEY}&s={TITULO}&type=movie. Para realizar uma chamada de teste você pode copiar essa URL, colar no seu navegador (ou em algum software como o Insomnia) e substituir {API_KEY} pela sua chave recebida no email e {TITULO} pelo nome do filme a ser buscado. Importante: Para query strings, utilizamos %20 invés de espaço, então Star Wars estaria como Star%20Wars na URL, por exemplo.

Fazendo uma busca por “V for Vendetta” recebemos este JSON de retorno:

https://gist.github.com/lucasfnicolau/698ce17b8347a62ebeeea7dd5906a1d3.js

E como fazer pra usar isso em Swift? É aí que o Codable entra! Simplesmente precisamos criar, no nosso caso, uma struct com os campos que iremos utilizar, escritos da mesma forma que as chaves do JSON.

Vamos iniciar esse processo criando um novo arquivo Swift, na pasta Model do nosso projeto, e o nomeando como Search:

Antes de tudo, comece escrevendo o esqueleto de nossa estrutura, lembrando de adicionar o protocolo Codable:

Para preencher esse arquivo, podemos fazer isso de duas formas, uma delas é ir escrevendo na mão os campos/propriedades e a outra forma é utilizando a ferramenta Quicktype.io, que converte JSONs para quase qualquer linguagem compatível. Basta colar um exemplo de JSON lá que ele gera nossa struct necessária. Vamos fazer na mão, para entendermos o passo-a-passo

Por qual motivo criamos o arquivo chamado Search? Pois, se olhar o JSON de retorno da API (visto um pouco acima), vemos que ele possui uma chave "Search" que consiste em um array de objetos, que serão nossos filmes.

O que vamos precisar utilizar desse JSON de pesquisa? Para fins deste tutorial, iremos utilizar uma propriedade chamada movies e uma response. Deixando a struct assim:

Porém… Lembra que comentei que as propriedades precisavam ter exatamente o mesmo nome das chaves do JSON que iremos utilizar? Então, no JSON essas chaves estão começando com letras maiúsculas, e sabemos que não devemos dar nomes de variáveis dessa forma. Além disso, uma das chaves é "Search", mas a nossa propriedade está nomeada como movies. Como proceder? Para isso existe o enum CodingKeys! Podemos criar ele dentro da nossa struct para dizer para o Swift qual serão as chaves equivalentes às nossas propriedades, olha que demais:

Dessa forma, estamos dizendo ao Swift que quando ele encontrar a chave "Search" é pra atribuir o valor dentro de movies e quando encontrar "Response", deve colocar a informação em response.

Ótimo! Isso quer dizer que já podemos utilizar essa struct? Ainda não, inclusive, com certeza tem um erro sendo mostrado no seu Xcode, pois precisamos ainda criar nossa struct Movie (similar à demonstrada no início deste artigo). Vamos fazer isso adicionando na pasta Model mais um arquivo chamado… Movie! Olha que surpreendente 😅.

Olhando o JSON da OMDb API, vemos que um filme é disposto assim:

Logo, nossa struct deve ser construída com isso em mente:

https://gist.github.com/lucasfnicolau/a9f7a0374b3ed01832c64c72dbad8470

Perceba que estamos utilizando o enum CodingKeys novamente, para podermos colocar nossas propriedades com o nome minúsculo, como deve ser. Também criamos um enum para o type, já que ele pode ser "movie", "game", além de outras coisas (apesar disso, só utilizaremos filmes aqui).

Uhul! Agora sim temos os dois Models necessários para podermos realizar a chamada da API e a decodificação do JSON recebido de resposta. Vamos começar esse procedimento adicionando no nosso MoviesViewController uma variável para armazenar nossos filmes e declarando uma função para realizar a busca na API:

https://gist.github.com/lucasfnicolau/7d162cb30f8d4220ab5db1980087de35

Já temos nossa função de busca declarada, então vamos terminar de preenchê-la. Para isso precisamos fazer apenas 4 coisas:

  1. Fazer a requisição da API através do endpoint;
  2. Decodificar o JSON, o transformando em um Movie;
  3. Adicionar esse Movie no nosso Array;
  4. Recarregar os dados da tableView.

Vamos começar:

https://gist.github.com/lucasfnicolau/d3504027cd59ffd117805b9ca318c842.js

Perceba que estamos usando uma função chamada formatToQueryString(_: String) no começo do código. Essa função foi criada por mim, para formatar nomes de filmes que possuam espaço, já que, como dito anteriormente, na hora de realizar a requisição, trocamos espaços em branco por %20, então essa função toma conta disso.

Seguindo os números nos comentários, temos:

  1. Criação da URL completa, utilizando o ENDPOINT base (pode encontrá-lo no arquivo Constants.swift), a sua API_KEY, o título do filme a ser buscado (queryStr) e o type=movie, já que estamos buscando apenas filmes;
  2. Acessamos a URL e guardamos os dados (data) de retorno, que será nosso conteúdo em JSON;
  3. Criamos um objeto do tipo Search através da decodificação do JSON, utilizando o JSONDecoder;
  4. Atribuímos os movies dessa search ao nosso Array e recarregamos os dados da tableView.

Perfeito! Agora só precisamos chamar nossa função searchMovies(named: String) na última linha da nossa @IBAction func searchButtonTouched(_: UIButton), passando name como parâmetro:

Bom, se você rodar o projeto agora, verá que, ao pesquisar, ainda não vemos nada na tableView, e isso obviamente acontece porque faltou a última parte deste tutorial, mexer no TableViewDataSource. Vamos abrir o arquivo MoviesTableViewDataSource e modificá-lo para que fique desse jeito:

https://gist.github.com/lucasfnicolau/857fb56e6379c9a0d60c51bb66a2d927.js

Detalhes importantes:

  1. Estamos retornando sempre pelo menos 1 célula na função tableView(..., numberOfRowsInSection), e isso está sendo feito porque o aviso de “Digite o nome de um filme (em inglês) na barra de busca e clique na lupa para pesquisar :D” está construído em uma célula de TableView, então precisa existir ao menos uma dela;
  2. Como sempre teremos ao menos uma célula, verificamos se o Array movies está vazio na função tableView(..., cellForAt) e, caso esteja, retornamos a célula com a dica de como pesquisar filmes. Se não estiver vazio, criamos uma célula de filme, preenchemos os campos necessários e… pronto!

Vale ressaltar que a função load(url: String) utilizada no posterImageView encontra-se no arquivo Extensions.swift e facilita o uso de imagens da internet. A função foi retirada do site Hacking with Swift.

Rode seu projeto, pesquise um nome de filme e veja a mágica acontecendo! Ufa! Conseguimos, chegamos ao fim.

Obrigado e até a próxima!

--

--

Lucas Fernandez Nicolau
Apple Developer Academy | Mackenzie

iOS Developer passionated about consistent software combined with a beautiful UI and a great UX. Currently working @ BTG Pactual