Keddit – Parte 4: RecyclerView – Delegar adaptadores e classes de dados com Kotlin
Conteudo
Introdução
Nesta 4ª parte, vamos cobrir estes tópicos sobre Kotlin:
- Construtor Init
- Expressões de Objeto
- Expressões Únicas
- Classes de dados
- Gamas
- Lista e Lambdas (introdução)
Cada tópico estará dentro do contexto de criação de nosso adaptador NewsAdapter e usando o padrão Adaptador Delegado.
Então, vamos começar a criar nosso adaptador e mostrar algo na tela!
Criando o NewsAdapter
Vamos criar um novo adaptador para nosso RecyclerView. Nesse caso, vamos fazer isso com um padrão chamado “Adaptador de delegado”, que usamos em nossa empresa e foi inspirado neste artigo de Hannes Dorfmann.
Nosso Adaptador terá uma lista de adaptadores delegados, os quais ficarão encarregados de saber como inflar e retornar uma determinada view no RecyclerView. A ideia geral por trás disso é receber uma lista de itens ViewType e, de acordo com este ViewType, obter o Adaptador Delegate correspondente para aumentar e preencher a visualização para este item. O item será usado para ser passado como argumento para o adaptador delegado para que o item possa ser usado para extrair dados específicos para esta Visualização.
A ideia principal por trás disso é combinar itens (objetos ViewType) com adaptadores específicos (adaptadores delegados) como você pode ver nesta imagem:
Graças ao ViewType, sabemos qual adaptador delegado precisamos usar para criar a View para este item. No nosso caso, os itens serão uma lista de notícias e no final da lista adicionaremos um item de carregamento para dar a idéia de que mais notícias estão por vir. Portanto, precisaremos de dois adaptadores delegados, um para Notícias e outro para Carregamento.
Esta abordagem dá a você muita flexibilidade para adicionar novas visualizações ao seu RecyclerView, basta adicionar um novo adaptador delegado que corresponde a um novo ViewType e isso é tudo! por exemplo, poderíamos adicionar um novo ViewType para Promoções e o Adaptador de Delegado correspondente para aumentar uma visualização com promoções.
Vamos continuar aprendendo mais sobre as classes e interfaces de que precisamos para fazer isso.
ViewType
É uma interface que usaremos para os itens que vamos mostrar no RecyclerView. Cada item deve implementar esta interface para que possamos solicitar a cada item o tipo de ViewType (valor int) e procurar o adaptador delegado correspondente a este tipo:
interface ViewType { fun getViewType(): Int }
Em nosso adaptador, teremos uma lista de ViewType chamada itens:
private var items: ArrayList<ViewType>
Aqui vamos armazenar as notícias e o item de carregamento. Nosso modelo de notícias se estenderá de ViewType e vamos criar um novo item localmente para o item Carregando.
Vamos adicionar um carregador à nossa visualização de reciclador
Precisamos de 3 coisas:
- O tipo de ViewType: será um valor inteiro (como um ID) para corresponder ao nosso ViewType com o adaptador delegado (AdapterConstants.LOADING).
- O Item ViewType: Este será um objeto que implementa a interface ViewType e retorna o Tipo (ID) de ViewType. Isso nos permitirá inserir este item na lista de itens e dizer ao adaptador para renderizar esta visualização.
- O Adaptador de Delegado de Carregamento: que se encarregará de inflar a visualização e devolvê-la ao nosso NewsAdapter.
Carregando Adaptador Impl delegado
class LoadingDelegateAdapter : ViewTypeDelegateAdapter { override fun onCreateViewHolder(parent: ViewGroup) = TurnsViewHolder(parent) override fun onBindViewHolder(holder: RecyclerView.ViewHolder, item: ViewType) { } class TurnsViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder( parent.inflate(R.layout.news_item_loading)) { } }
Como você pode ver, estamos criando nosso Adaptador de delegado estendendo outra interface chamada “ViewTypeDelegateAdapter”. Essa interface nos permite ter uma lista genérica de adaptadores delegados e invocar esses métodos sem exigir que tenhamos em nosso NewsAdapter para saber nada específico da implementação dos adaptadores delegados. Um método para criar o ViewHolder e outro para vinculá-lo.
Vincular o tipo ViewType com um adaptador de delegado
Vamos usar um mapa para vincular um tipo ViewType a um adaptador delegado (isso será na classe NewsAdapter)
private var delegateAdapters = SparseArrayCompat<ViewTypeDelegateAdapter>() init { delegateAdapters.put(AdapterConstants.LOADING, LoadingDelegateAdapter()) ... }
Init Constructor
init é a palavra reservada em Kotlin para um construtor de uma classe (neste caso NewsAdapter), aqui vamos inicializar este mapa adicionando cada tipo de ViewType com o adaptador delegado correspondente. Nesse caso:
AdapterConstants.LOADING > LoadingDelegateAdapter()
Para criar um novo objeto em Kotlin, você não precisa usar a palavra-chave “new”.
Carregando o item ViewType
Vamos criar nosso item de carregamento, este item nos ajudará a renderizar a visualização de carregamento na posição em que o inserirmos na lista de itens:
private val loadingItem = object : ViewType { override fun getViewType() : Int { return AdapterConstants.LOADING } }
Adicione também este item por padrão como o primeiro item a ser processado na lista:
init { delegateAdapters.put(...) items = ArrayList() items.add(loadingItem) }
Object Expressions
No Kotlin, você tem algo chamado “Object Expressions”, que funciona de maneira semelhante às classes internas anônimas em Java e permite criar um objeto sem declarar explicitamente uma nova subclasse para ele. Neste caso, estamos usando para definir nosso loadingItem sem criar uma nova classe. A sintaxe é muito intuitiva e como você pode ver, estamos estendendo de ViewType e implementando a interface necessária.
Single Expressions
O método getViewType() possui apenas uma única função de expressão dentro do corpo. Em Kotlin, podemos aproveitar isso e converter este método:
override fun getViewType() : Int { return AdapterConstants.LOADING }
Para isso:
override fun getViewType() = AdapterConstants.LOADING
É como se estivéssemos atribuindo o valor AdapterConstants.LOADING a uma função. Esta é uma maneira curta de fazer o mesmo e é realmente mais conciso. Além disso, você não precisa especificar o tipo de valor de retorno, pois pode ser inferido pelo contexto. Então é assim que parece agora:
private val loadingItem = object : ViewType { override fun getViewType() = AdapterConstants.LOADING }
Artigo de notícias com classes de dados
Antes de criar nosso adaptador de delegado News e configurar o NewsAdapter para receber uma lista de notícias, precisamos de nosso objeto de UI para representar cada notícia. Em Java, normalmente criaríamos uma classe como esta:
public class RedditNewsItem { private String author; private String title; public MyNews(String author, String title) { this.author = author; this.title = title; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
Mais uma vez, Kotlin veio para nos ajudar com um novo tipo de classe chamado “data class” que traz muitos benefícios e para o mesmo exemplo se parece com isto:
data class RedditNewsItem(var author: String, var title: String)
Com essa linha simples, você está fazendo o mesmo que o código Java anterior, isso significa que autor e título têm seus próprios getters e setters e requer um construtor com esses dois elementos. Isso é simplesmente INCRÍVEL! mas espere! você ainda receberá mais benefícios se fizer esta aula de dados, receberá gratuitamente por esta aula:
- equals/hashCode par.
- toString() com todas as propriedades incluídas.
- copy() método para fazer facilmente uma cópia do seu objeto.
- e outros métodos úteis que não veremos aqui, mas você pode ver mais na página oficial.
Criando NewsDelegateAdapter
Agora que criamos nosso item de notícias, precisamos de nosso adaptador de delegado, que será responsável pela criação da visualização. Aqui você tem uma prévia do que precisamos:
Então precisamos
- Mostra alguns textos como título, autor, comentários, etc.
- Uma imagem, vamos usar o Picasso (veja o arquivo build.gradle aqui)
- Uma função de extensão para mostrar a hora desta forma.
Vamos revisar algumas coisas do Kotlin:
Função de extensão para Picasso
Me permite fazer um ImageView para carregar sua imagem diretamente:
fun ImageView.loadImg(imageUrl: String) { if (TextUtils.isEmpty(imageUrl)) { Picasso.get().load(R.mipmap.ic_launcher).into(this) } else { Picasso.get().load(imageUrl).into(this) } }
Função de extensão por tempo
Estaremos recebendo do Reddit, o tempo de um comentário em formato longo, então o que estamos fazendo aqui é converter um tipo Longo em uma String com o formato “3 dias e 1 minuto atrás”:
fun Long.getFriendlyTime(): String { // lógica aqui ... }
Verifique o arquivo chamado “TimeExt.kt” para ver o código. Com certeza, pode ser melhorado, mas para o propósito deste exemplo é perfeito 🙂
Extensões Android para visualização de notícias
Verifique o arquivo “NewsDelegateAdapter.kt” e você verá que também estamos usando extensões Android para esta visualização (como fizemos com o NewsFragment), mas neste caso estamos adicionando ao pacote sintético um valor extra chamado “visualização” no fim do nome do layout:
import kotlinx.android.synthetic.main.news_item.view.*
É assim que as extensões do Android permitem que você ainda vincule uma visualização com o código sem estar no contexto de uma atividade ou fragmento (como neste caso).
Atualize o NewsAdapter para receber notícias
Vamos modificar nosso NewsAdapter para receber uma lista de notícias simuladas a serem mostradas com nossas notícias e adaptadores delegados de carregamento:
Range
O Kotlin permite que você crie de uma maneira fácil um intervalo de números (tipos Int, Long e Char) usando uma expressão como esta: “1..10”, isso retornará um IntRange (como estamos usando Ints neste exemplo) , IntRange estende-se de IntProgression e implementa Iterable. Graças a isso, podemos iterar no intervalo que temos, neste caso de 1 a 10, para criar nossos dados fictícios:
for (i in 1..10) { ... }
Você pode controlar as etapas no intervalo e torná-lo decremental como “10 downTo 1”. Mais sobre isso você pode encontrar aqui.
mutableListOf
Esta é uma função Kotlin que retorna uma MutableList, uma lista que pode ser modificada e, neste caso, é para armazenar localmente as notícias simuladas para o adaptador:
val news = mutableListOf<RedditNewsItem>()
Lista funções e lambdas
Criei um método que usarei mais tarde para salvar as notícias quando estivermos no meio de um evento de Mudança de Orientação, mas gostaria de mostrar como é fácil trabalhar com lista para filtrar e mapear (transformar) cada item de uma lista para outra.
Em nosso código, temos um método chamado “getNews” que retorna uma lista de RedditNewsItems. Para filtrar e transformar nossos itens (uma lista de ViewType), vamos fazer isso:
fun getNews(): List<RedditNewsItem> { return items .filter { it.getViewType() == AdapterConstants.NEWS } .map { it as RedditNewsItem } }
Filter
Cada lista tem algumas funções úteis como “filter”, neste caso, que nos permite iterar uma lista e filtrar (excluir) itens que não se aplicam a certas condições. Em nossa lista de itens, temos ViewTypes para que possamos ter itens de notícias ou itens de carregamento, com esta função de filtro, garantimos retornar apenas aqueles que são itens de notícias.
Map
Outra ótima função é “map” para transformar os itens de uma lista. Nesse caso, lançamos um objeto ViewType para um RedditNewsItem, mas também podemos criar e retornar novos objetos.
Lambdas
.map { it as RedditNewsItem }
Map não é outra coisa senão uma função que recebe como primeiro parâmetro uma função, mas Kotlin o torna ótimo, pois permite excluir parênteses e definir um bloco de código próximo ao nome da função, no final este bloco de código será o primeiro parâmetro exigida pela função e o bloco de código é a famosa expressão Lambda, uma função que não é declarada, mas passada imediatamente como uma expressão
Não vamos entrar em mais detalhes sobre Listas e Lambdas, mas acho que é realmente um bom ponto de partida para ver esse tipo de exemplo de como usar esses ótimos recursos. Posteriormente neste tutorial, levaremos mais tempo para falar sobre isso.
Conclusão
Eu sei que passei mais tempo explicando o padrão Adaptador Delegado do que os recursos específicos do Kotlin, mas com certeza considero que esse padrão é realmente um excelente padrão que merece ser apresentado com o código Kotlin, mas espero que você tenha aprendido novos recursos do Kotlin com este novo código. Além disso, nosso código está ficando melhor (ou uma lista mostrando algo na interface do usuário, lol).
Não hesite em contactar-me para qualquer questão, tentarei fazer o meu melhor para lhe dar uma resposta.
Twitter: https://twitter.com/caneto