Mostrando os Pokémon com Coil em Android

Ronaldo Costa
7 min readNov 3, 2022

--

Logo do Coil

No último post, nós conseguimos construir uma arquitetura MVVM simples, conectar nosso app a PokéAPI e listar os pokémon de Kanto por meio de log. Mas não listamos os pokémon na tela da nossa Pokédex muito menos mostramos as imagens dos monstrinhos. Sendo assim, nesse post vamos refatorar um pouco nosso código, implementar uma Recycler View e usar o Coil para exibir os pokémon na tela.

Corrigindo um pequeno erro

Anterioirmente, eu acabei cometendo um erro no mapeamento da API, especificamente na estrutura do JSON que continha a imagem que queremos usar do pokémon. Eu mapeei assim:

O que deveria ser assim:

Para corrigir isso, precisamos fazer alguns ajustes simples: primeiro vamos a classe Other:

Após isso, basta modificar a classe Sprites dessa forma:

E, por fim, corrigir o mapeamento front-default da classe OfficialArtWork para front_default:

Pronto: agora nosso mapeamento da api está correto!

Refatorando o código

Outro “erro” do último post foi misturar os conceitos de classes Models e classes DTO (Data Transfer Object) ao fazer o mapeamento da API. Classes DTO são um padrão de software com o intuito de transferir informações entre as camadas de um sistema. Ele serve, por exemplo, para receber dados em um estado muito específico sem fazer contato entre as camadas inferiores da aplicação. No nosso caso, ele é útil justamente para serializar o JSON recebido como resposta pela PokéAPI. Sendo assim, vamos renomear todas as nossas antes classes Models para classes DTO e colocá-las em um pacote específico dto:

Classes DTO

Agora que já temos nossas classes DTO, vamos realmente fazer nossas classes Models, que servirão como receptoras dos dados armazenados pelas DTO. Primeiro temos a classe que irá representar um único pokémon, a SinglePokemon:

Agora vamos desenvolver a classe para representar os tipos do pokémon, a classe Type:

Bem simples, não? Essas duas classes Model contém todos os dados dos pokémon que nós vamos precisar no momento. Agora vamos criar funções para mapear classes DTO para classes Models em um arquivo chamado PokedexMappers. A primeira delas é uma função para mapear uma SlotTypeDTO para uma Type:

Repare que nós usamos uma função chamada emptyString(), ela retorna apenas uma string vazia, como seu nome diz, fazemos isso para tornar nosso código mais idiomático, aqui seu código em um arquivo chamado StringExt:

Agora vamos fazer uma função para mapear uma lista de SlotTypeDTO para uma lista de Type:

Essa é uma pouco mais complexa, mas o que fazemos aqui é criar uma MutableList de Type e adicionar o primeiro tipo do pokémon, depois analisamos se ele tem um segundo tipo obtendo o último elemento da lista de SlotTypeDTO, caso esse elemento seja diferente do primeiro, isso significa que há um segundo tipo, então nós o adicionamos na lista, caso seja igual, não há um segundo tipo, então nós adicionamos uma string vazia. Por fim, retornarmos a lista como uma List<Type>.

Agora basta mapearmos uma SinglePokemonDTO para uma SinglePokemon:

Note que também usamos uma função zeroNumber() para deixar nosso código mais legível. Eis o código dela no arquivo IntExt:

Agora temos que substituir a menção a essas classes DTO no código para suas respectivas models, com especial atenção para a seguinte:

Aqui nós usamos a função que mapeia para um único pokémon: em resumo, mudamos o tipo de retorno de getSinglePokemon() para SinglePokemon.

Por fim, mas não menos importante, vamos renomear nossa interface PokemonService para PokemonApi, com o intuito de deixar claro de que se trata da interface que contém as chamadas à PokéAPI:

E pronto: refatoramos nosso código! Agora ele está melhor do que antes nos quesitos de legibilidade e consistência de dados.

Construindo a Recycler View

Para a nossa Pokédex ser realmente uma Pokédex, precisamos mostrar nossos monstrinhos de bolso como uma lista e para isso existem diversas soluções em Android, como a Recycler View. Vamos usa ela por sua eficiência em exibir conjuntos grandes e dinâmicos de dados.

Com Recycler View nós fornecemos os dados e definimos a aparência de cada item, após isso, como o próprio nome indica, esses dados são reciclados: quando um item rola para fora da tela, o Recycler reutiliza sua visualização para novos itens que passarem a aparecer na tela. Isso melhora muito o desempenho, aperfeiçoando a capacidade de resposta do app e reduzindo o consumo de energia. Mais informações aqui: developer.android.

Implementar um Recycler View não é uma tarefa trivial: é necessário (1) primeiro decidir se será uma lista ou uma grade, (2) depois criar a aparência e o comportamento de cada elemento da lista, (3) estender da classe ViewHolder, responsável por fornecer todas as funcionalidades para os itens da lista, e por fim, (4) definir o Adapter que associa seus dados à visualização ViewHolder. Ufa, bastante coisa, não? Então vamos começar logo!

Para início de conversa, vamos fazer a nossa pokédex como uma lista por enquanto e definir como será a aparência dos itens. Sendo assim, criamos o layout pokemon_card.xml da seguinte forma:

É um layout bem simples: temos a imagem do pokémon, seu nome, seu id e seus tipos. Repare que o segundo tipo está gone como padrão, ou seja, não está na tela, faremos assim pois nem todo pokémon têm dois tipos. Com esse layout teremos esse resultado:

Preview do pokemon_card.xml

Em seguida vamos adicionar a ViewGroup do RecyclerView na nossa pokedex_activity_xml e informar que seu listitem é o layout que acabamos de construir:

Está feito, agora vamos criar nosso Adapter e por consequência estender e personalizar a ViewHolder, vamos chamar essa classe de PokedexAdapter:

Ok, dessa vez o código é bem grande, vamos por partes. Primeiro de tudo, nossa classe estende de RecyclerView.Adapter e usa PokedexViewHolder para fazer o bind (ligação) de dados. Essa nossa classe tem duas propriedades: um context, que será usado para inflar o layout, e uma lista de pokémon, nossa pokédex.

Sendo assim, definimos nossa ViewHolder, que usa view binding para ter acesso às view do layout e tem todas as propriedades necessárias para representar um pokémon. Nossa ViewHolder também tem o método bind(), que serve justamente para ligar os dados da lista de pokémon com as views do layout. Repare que nele usamos uma função chamada showIf(), essa função serve para tornar um TextView visível de acordo com uma condição, nesse caso se o conteúdo não for uma string vazia. Segue o código contido no arquivo TextViewExt:

Note que temos três funções sobreescritas: onCreateViewHolder(), onBindViewHolder() e getItemCount(), esses métodos são o motor da RecyclerView. Abaixo seguem as funções de cada uma de acordo com o site oficial dos desenvolvedores android:

1. onCreateViewHolder(): RecyclerView chama esse método sempre que precisa criar um novo ViewHolder. O método cria e inicializa o ViewHolder e a View associada, mas não preenche o conteúdo da visualização. O ViewHolder ainda não foi vinculado a dados específicos.
2. onBindViewHolder(): RecyclerView chama esse método para associar um ViewHolder aos dados. O método busca os dados apropriados e usa esses dados para preencher o layout do fixador de visualização. Por exemplo, se a RecyclerView exibir uma lista de nomes, o método poderá encontrar o nome apropriado na lista e preencher o widget TextView do fixador de visualização. No nosso caso, os dados preenchidos são de um pokémon específico.
3. getItemCount(): a RecyclerView chama esse método para ver o tamanho do conjunto de dados. Por exemplo, em um app de lista de endereços, pode ser o número total de endereços. O RecyclerView usa essa função para determinar quando não há mais itens a serem exibidos.

Ufa, finalmente terminamos nosso adapter, agora vamos configurar a RecyclerView na nossa activity. Para isso basta que definamos seu layout manager e o adapter que acabamos de construir. O layout manager por enquanto será o LinearLayoutManager, visto que vamos exibir uma lista. O código que configura a RecyclerView está contido na função setUpPokedexRecyclerView():

Com isso já estamos quase acabando nosso trabalho de hoje, falta apenas exibirmos as imagens dos nossos monstrinhos com o Coil.

O que é Coil?

Coil é uma biblioteca de carregamento de imagens construída com Kotlin Coroutines. Ela é rápida, leve (adiciona mais ou menos 2000 métodos para a APK, o que comparado ao Picasso e Glide é significantemente menor), fácil de usar e moderna (Coil foi feita em Kotlin e usa bibliotecas modernas como Coroutines, OkHttp, Okio and AndroidX Lifecycles.

E é por isso que vamos usá-la.

Curiosidade: Coil é o acrônimode Coroutine Image Loader

Mostrando os Pokémon com Coil

De início precisamos incluir o Coil como uma dependência no nosso arquivo gradle:

Após sincronizarmos nosso arquivo gradle, já temos acesso às funções do gradle. Fique tranquilo, carregar as imagens dos pokémon será a parte mais fácil de hoje, analise o código abaixo:

Conseguiu notar o que mudou? Nós apenas tivemos que usar a função load() do Coil para carregar cada imagem dos nossos pokémon e extraímos esse código para a função loadPokemon() 😱

E pronto! Basta rodarmos o app para vermos o resultado:

Incrível né? Nós finalmente temos uma pokédex minimamente visualizável, parabéns!

Próximos posts

Nossa pokédex, apesar de visualizável, ainda está bem feia e lenta, levou cerca de 20 segundos para todas as imagens dos monstrinhos de bolso serem carregadas. Nós próximos posts vamos melhorar sua performance e deixá-la mais bonita.

Link do repositório no github:

Post anterior:

Próximo post:

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

--

--

Ronaldo Costa

I’m an Information Systems Graduating Student at the Amazonas State University. Currently, I’m working as an Android Software Developer Intern at iFood