Construindo uma Pokédex com Android
Tags: kotlin, android, pokémon
Published: 2022-10-28 01:50:31

Hoje começo uma série de posts em que vamos construir uma Pokédex com Android e a API PokéAPI onde vamos aprender muitas coisas novas, como arquitetura MVVM (Model-View-ViewModel), coroutines, comunicação com APIs usando Retrofit e carregamento de imagens com Coil.

Nesse primeiro post, vamos focar em criar os models para representar o JSON (JavaScript Object Notation) de retorno da PokéApi ao pedirmos um GET na url `https://pokeapi.co/api/v2/{id}`, nós comunicar com a API usando Retrofit e listar como LOG os 151 Pokémon da 1° geração.

## Resumão MVVM (Model-View-ViewModel)

MVVM é um padrão de arquitetura de desenvolvimento Android adotado por quase toda a indústria e é baseado em três camadas: Model, View e ViewModel.


![Arquitetura MVVM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lsa4c56d56d2ttipwi4g.png)

**Model**: representa os dados e a lógica de negócios da aplicação Android. Ela consiste da lógica de negócios — fonte de dados local e remota, classes models e repositories.

**View**: é basicamente o código da UI (Activities, Fragments), XML. Envia a ação do usuário para a ViewModel mas não recebe a resposta de volta diretamente. Para conseguir a resposta, ela deve consultar os observables os quais a ViewModel expõe.

**ViewModel**: é a ponte entre a View e a Model (lógica de negócios). Ela não tem a menor ideia de qual View a usa, assim como não tem uma referência direta para a View. Então, basicamente, a ViewModel não deve estar ciente da View que está interagindo com ela. Desse modo, ela interage com a Model e expõe os observables que podem ser consultados pela View.

## Criando o projeto

Agora que entendemos um pouco sobre MVVM vamos iniciar o desenvolvimento do nosso projeto.  Como estamos desenvolvendo um app Android Nativo, vamos usar o Android Studio e Kotlin.

Para começar, nós podemos escolher se o nosso projeto já vai ter alguma Activity desenvolvida por padrão. Para esse primeiro post não importa muito, podemos tanto escolher `No Activity` quanto `Empty Activity`, a única diferença é que se escolhermos a primeira opção vamos ter um trabalhinho a mais no final para criarmos a activity, mas isso é de boa.


![Tela inicial do projeto](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kjusgi3x3g7g2jsvmdtw.png)

Depois, damos um nome para nosso app, o nome do seu pacote e escolhemos seu local de armazenamento. No nosso caso, vamos chamar de `pokedex`, mantemos o resto das configurações como padrão, clicamos em `Finish` e pronto: nosso projeto está criado!


![Tela de configuração do projeto](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u0kkqmp3lulzc7319w7b.png)

## Configurando o arquivo gradle

Após criarmos nosso projeto, precisamos configurar o arquivo `build.gradle` para adicionarmos algumas dependências e configurações.

De forma bem resumida, o `Gradle` é uma build tool responsável por configurar todo o processo de build do Android: todas as bibliotecas, plugins, etc.

Primeiro nós configuramos que queremos usar `View Binding` no nosso projeto. View Binding é um recurso que facilita a programação de códigos que interagem com views, ao ser ativada, ela gera uma classe de vinculação parar cada arquivo de layout XML. A instância de uma classe de vinculação contém referências diretas a todos as views que têm no código do layout correspondente. Na maioria dos casos, o View Binding substitui o uso do método `findViewById()`.

{% embed https://gist.github.com/ronaldocoding/b73dc18fb98c2ba388c6237e99de9530 %}

Agora vamos adicionar as dependências que precisamos: Retrofit, Gson, Coroutines, ViewModel e LiveData:

{% embed https://gist.github.com/ronaldocoding/f4c193051e7f7c840af66f83f8881fc9 %}

Feito! Basta clicarmos em `Sync Now` e nosso arquivo gradle estará configurado e sincronizado.

## Construindo os models

Para construir a camada Model, primeiro precisamos analisar a estrutura do JSON retornado nas requisições GET que vamos fazer na PokéAPI.

A estrutura do resultado de GET `https://pokeapi.co/api/v2/{1}` é a seguinte:

![Estrutura parte 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y26nrpo2sged2xjkhu8j.png)
![Estrutura parte 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eacnyj9ru2sak3srdmgn.png)

O JSON retornado é bem grande, esse do Bulbasaur chega a 11451 linhas! Mas para nossa Pokédex, vamos usar apenas alguns campos, de forma que o nosso JSON vai ficar mais ou menos assim:

{% embed https://gist.github.com/ronaldocoding/7bf380d7441c22380e3c12b8937c910d %}

Agora que sabemos como é a estrutura do nosso JSON, já podemos criar nossas models como data classes. Data classes são um recurso muito bom do Kotlin que nos ajuda evitar escrever código boilerplate, ou seja, código repetitivo, comum de qualquer implementação. Basicamente, são classes que tem o intuito de armazenar dados e criam métodos úteis para todas as suas propriedades, como `toString()`, `hashCode()` e `copy()`. 

A nossa primeira data class delas é a `SinglePokemonResponse`:

{% embed https://gist.github.com/ronaldocoding/da375b15ddc9edeef62e7bb784f3e341 %}

Ela representa justamente o JSON que recebemos com resposta ao fazer a requisição GET descrita anteriormente. A annotation `@SerializedName()` é um recurso do Retrofit para indicar que essa propriedade em específica deve ser serializada para JSON com o nome fornecido no seu campo, em outras palavras, ela mapeia uma chave-valor da estrutura JSON recebida.

Seguem as outras data classes que compõem `SinglePokemonResponse`:

`Sprites`:
{% embed https://gist.github.com/ronaldocoding/07f20a90a6c500639048955e12c92c28 %}

`OfficialArtwork`:
{% embed https://gist.github.com/ronaldocoding/c2f9714116df8e07ea9a67b5e8d9249a %}

`SlotType`:
{% embed https://gist.github.com/ronaldocoding/96ea83e3ed0632c92cf1b79c76bce562 %}

`Type`:
{% embed https://gist.github.com/ronaldocoding/0d8e3b0edc7a3976c755fecad61bb790 %}

Pronto: terminamos nossos models!

## Criando o PokemonService

Chegou a hora de criarmos nosso service. O service se trata de uma interface que contém as funções que farão as requisições HTTP usando o Retrofit, no nosso caso, será apenas a de pegar as informações de um Pokémon específico:

{% embed https://gist.github.com/ronaldocoding/3b3480520dbfd2722f8211f9ba453342 %}

Aqui nós usamos mais duas annotations do Retrofit: a `@GET`, que indica que essa função fará uma requisição GET, e a `@Path`, que indica que a propriedade a seguir irá substituir no caminho da url, nesse caso é o id do Pokémon.

Note que nós usamos uma `suspend fun`, a palavra-chave `suspend` significa que essa função pode ser bloqueada, suspendida, ela pode apenas ser usada no contexto de Coroutines. Em resumo, Coroutines nos permitem criar programas assíncronos de uma forma fluída, em Android elas servem como threads leves para gerenciar a chamada assíncrona das nossas requisições HTTP e definir quais threads devem ser executadas, evitando assim que o nosso app rode de forma perigosa na Thread Main do device e cause crashs.

Bem simples, não? Nosso PokemonService já está pronto.

## Criando o PokemonRepository

Agora vamos partir para a criação do nosso repository. Um repository serve para acessarmos nossa fonte de dados, seja ela remota ou local. No nosso caso, como estamos consumindo uma API, é uma fonte de dados remota.

Nosso `PokemonRepository` é basicamente uma classe que tem a propriedade `service` do tipo `PokemonService` e buildamos uma instância do Retrofit. Após isso, acessamos nossa fonte de dados remota a partir da função `getSinglePokemon()`:

{% embed https://gist.github.com/ronaldocoding/c37adfa47b6c2c4bb4b6179c6f0d37d5 %}

Para entendermos o que aconteceu, vou explicar passo-a-passo:

1. primeiro criamos o builder do Retrofit passando a url base da API,
2. depois adicionamos o Gson como converter para transformar o JSON em objeto e
3. por fim, usamos o create , no qual informamos nossa interface PokemonApi.

Dessa forma, já temos o poder do Retrofit para fazermos as requisições.

E assim, terminamos nosso PokemonRepository e por consequência nossa camada Model!

## Construindo nossa PokedexViewModel

Vamos fazer nossa ViewModel. Como dito anteriormente, a ViewModel é a ponte entre a Model e a View, sendo assim, a responsabilidade é expor observables com dados da Model para serem consultados pela View. 

Sendo assim, na nossa `PokedexViewModel` vamos pegar os dados da Model através do `PokemonRepository` e depois vamos expor o observable chamado `pokemon` que é composto por um  `MutableLiveData _pokemon`, que devolve uma lista de Pokemon, o Mutable no nome indica que essa variável pode ser alterada, e também é composto por um `LiveData pokemon`, diferentemente do MutableLiveData, esse LiveData é imutável, ela basicamente é o observable que está exposto para consumo da View, fazemos desse jeito por um questão de seguranção, afinal não queremos que a lista de Pokemon seja alterada, apenas consumida.

{% embed https://gist.github.com/ronaldocoding/7c4dc6a5a26f6fe4a0e26fd5beae5d7e %}

Note que usamos o escopo da ViewModel `viewModelScope` para indicar qual Thread queremos usar através do `Dispatchers.IO` da Coroutine. No nosso caso, como é uma requisição HTTO, o indicado é usar a Thread de IO para não sobrecarregar a Main Thread e corrermos o risco de termos um crash no app.

Após isso, para atualizar e expor nosso LiveData, trocamos para a Thread Main a partir da função da Coroutines `withContext()`.

Pronto, já temos nossa PokedexViewModel!

## Finalmente listando os Pokémon

FINALMENTE vamos listar nossos Pokémon! Para isso, precisamos começar a fazer algo básico, que é habilitar a permissão de acesso a internet, visto que vamos acessar uma API. Para isso basta acessarmos o `AndroidManifest.xml` e escrever o seguinte:

{% embed https://gist.github.com/ronaldocoding/aee0906c58aebbbdb1996d853514fc92 %}

Após isso, aos que escolheram criar o app  com a opção `No Activity`, precisamos criar nossa Activity, para isso bastar criarmos uma classe chamada `PokedexActivity`:

{% embed https://gist.github.com/ronaldocoding/ac598f4dba4766e90e4263f2a3a345eb %}

Nessa Activity, nós fazemos várias coisas, então vamos passo-a-passo. Primeiro criamos nossa variável da ViewBinding, vamos usá-la para inflar nosso layout. Depois, criamos uma variável da ViewModel como `lateinit var` ou seja, para ser inicializada posteriormente.

Chegou a hora tão esperada! Para podermos usar nossa ViewModel utilizamos o `ViewModelProvider` que é responsável por prover/injetar uma instância da classe `PokedexViewModel` para nós.

Após isso, chamamos o método `getPokemon()` da ViewModel e pronto: já temos nossos Pokémon! Agora basta observarmos o LiveData. Para fazer isso, utilizamos o método `observe()` ,  assim, ele vai ficar observando qualquer alteração nessa variável que irá acontecer dentro da ViewModel. Toda vez que o valor for alterado, o conteúdo do `Observer` será executado.

Por fim, dentro do Observer, percorremos a lista de Pokémon e para cada um deles, imprimimos seu nome usando `Log()` e pronto: se você escolheu a opção de criar o projeto com uma `Empty Activity` seu trabalho termina aqui!

Mas se você não fez isso, siga os próximos passos: (1) crie um arquivo xml chamado `activity_pokedex.xml`, não precisa alterar nada nele, e (2) declare sua Activity criada com Main e Launcher no `AndroidManifest.xml`.

`activity_pokedex.xml`:
{% embed https://gist.github.com/ronaldocoding/572dbf77ab326b06deb87a0fb8cbe38d %}

Alteração no `AndroidManifest.xml`:
{% embed https://gist.github.com/ronaldocoding/b7740febe815030d9065d1e896f0b8b2 %}

Agora sim! Se rodarmos nosso app e observarmos o Logcat, veremos os nomes do nossos Pokémon:


![Listagem de Pokémon](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ypl6uv3rt0x7cylyqbg.png)

Parabéns, demos um passo importante na construção da nossa Pokédex 🤓👍

## Próximos posts

Esse é o primeiro post de uma série de outros em que vamos construir uma Pokédex muito legal. Percebam que nosso código tem várias limitações: não temos testes, poderíamos criar UseCases para separar um pouco do código da ViewModel e precisamos melhorar nossa comunicação com a API, visto que fazemos 151 requisições para pegar as informações dos Pokémon, isso é muito ineficiente, tanto que optei por limitar os Pokémon apenas para os da 1º geração, porque fazer quase mil requisições para pegar todos os Pokémon seria inviável. São pontos para melhorarmos no futuro.

Repo no github:

{% embed https://github.com/ronaldocoding/pchunter-api %}

Próximo post:

{% embed https://dev.to/ronaldocoding/mostrando-os-pokemon-com-coil-em-android-35in %}

Obrigado pela atenção, até a próxima!