Conceitos básicos do Kotlin para Android 07.2: DiffUtil e vinculação de dados com a RecyclerView

Este codelab faz parte do curso Conceitos básicos do Kotlin para Android. Você vai aproveitar mais este curso se fizer os codelabs em sequência. Todos os codelabs do curso estão listados na página inicial dos codelabs de princípios básicos do Kotlin para Android.

Introdução

No codelab anterior, você atualizou o app TrackMySleepQuality para mostrar dados sobre a qualidade do sono em um RecyclerView. As técnicas que você aprendeu ao criar seu primeiro RecyclerView são suficientes para a maioria dos RecyclerViews que mostram listas simples que não são muito grandes. No entanto, há várias técnicas que tornam o RecyclerView mais eficiente para listas grandes e facilitam a manutenção e extensão do código para listas e grades complexas.

Neste codelab, você vai criar o app de rastreamento do sono do codelab anterior. Você vai aprender uma maneira mais eficaz de atualizar a lista de dados de sono e como usar a vinculação de dados com RecyclerView. Se você não tiver o app do codelab anterior, faça o download do código inicial para este codelab.

O que você já precisa saber

  • Criar uma interface básica do usuário usando uma atividade, fragmentos e visualizações.
  • Navegar entre fragmentos e usar safeArgs para transmitir dados entre eles.
  • Ver modelos, fábricas de modelos, transformações, LiveData e os observadores deles.
  • Como criar um banco de dados Room, um DAO e definir entidades.
  • Como usar corrotinas para banco de dados e outras tarefas de longa duração.
  • Como implementar um RecyclerView básico com um Adapter, ViewHolder e um layout de item.

O que você vai aprender

  • Como usar DiffUtil para atualizar de forma eficiente uma lista exibida por RecyclerView.
  • Como usar a vinculação de dados com RecyclerView.
  • Como usar adaptadores de vinculação para transformar dados.

Atividades deste laboratório

  • Crie com base no app TrackMySleepQuality do codelab anterior desta série.
  • Atualize o SleepNightAdapter para atualizar a lista de maneira eficiente usando DiffUtil.
  • Implemente a vinculação de dados para o RecyclerView usando adaptadores de vinculação para transformar os dados.

O app de monitoramento do sono tem duas telas, representadas por fragmentos, conforme mostrado na figura abaixo.

A primeira tela, mostrada à esquerda, tem botões para iniciar e interromper o rastreamento. A tela mostra alguns dos dados de sono do usuário. O botão Limpar exclui permanentemente todos os dados que o app coletou do usuário. A segunda tela, mostrada à direita, é para selecionar uma classificação de qualidade do sono.

Esse app foi arquitetado para usar um controlador de UI, ViewModel e LiveData, e um banco de dados Room para manter os dados de sono.

Os dados de sono são mostrados em um RecyclerView. Neste codelab, você vai criar a parte de vinculação de dados e DiffUtil para o RecyclerView. Depois deste codelab, seu app vai ter a mesma aparência, mas será mais eficiente e fácil de escalonar e manter.

Você pode continuar usando o app SleepTracker do codelab anterior ou fazer o download do app RecyclerViewDiffUtilDataBinding-Starter no GitHub.

  1. Se necessário, faça o download do app RecyclerViewDiffUtilDataBinding-Starter (link em inglês) no GitHub e abra o projeto no Android Studio.
  2. Execute o app.
  3. Abra o arquivo SleepNightAdapter.kt.
  4. Inspecione o código para se familiarizar com a estrutura do app. Consulte o diagrama abaixo para recapitular o uso de RecyclerView com o padrão de adaptador para mostrar dados de sono ao usuário.

  • Com base na entrada do usuário, o app cria uma lista de objetos SleepNight. Cada objeto SleepNight representa uma única noite de sono, a duração e a qualidade dela.
  • O SleepNightAdapter adapta a lista de objetos SleepNight para algo que o RecyclerView possa usar e mostrar.
  • O adaptador SleepNightAdapter produz ViewHolders que contêm as visualizações, os dados e as metainformações para que a visualização de reciclagem mostre os dados.
  • O RecyclerView usa o SleepNightAdapter para determinar quantos itens serão mostrados (getItemCount()). O RecyclerView usa onCreateViewHolder() e onBindViewHolder() para receber os titulares de visualização vinculados aos dados para exibição.

O método notifyDataSetChanged() é ineficiente

Para informar ao RecyclerView que um item na lista mudou e precisa ser atualizado, o código atual chama notifyDataSetChanged() no SleepNightAdapter, conforme mostrado abaixo.

var data =  listOf<SleepNight>()
   set(value) {
       field = value
       notifyDataSetChanged()
   }

No entanto, notifyDataSetChanged() informa a RecyclerView que toda a lista pode ser inválida. Como resultado, o RecyclerView faz uma nova vinculação e redesenha todos os itens da lista, incluindo aqueles que não estão visíveis na tela. Isso é muito trabalho desnecessário. Para listas grandes ou complexas, esse processo pode levar tempo suficiente para que a tela pisque ou gagueje enquanto o usuário rola a lista.

Para corrigir esse problema, diga ao RecyclerView exatamente o que mudou. Em seguida, o RecyclerView pode atualizar apenas as visualizações que mudaram na tela.

O RecyclerView tem uma API avançada para atualizar um único elemento. Você pode usar notifyItemChanged() para informar ao RecyclerView que um item mudou, e funções semelhantes para itens adicionados, removidos ou movidos. Você pode fazer tudo manualmente, mas essa tarefa não é trivial e pode envolver um pouco de código.

Felizmente, existe uma maneira melhor.

O DiffUtil é eficiente e faz o trabalho pesado para você

O RecyclerView tem uma classe chamada DiffUtil, que calcula as diferenças entre duas listas. O DiffUtil usa uma lista antiga e uma nova para descobrir o que é diferente. Ele encontra itens que foram adicionados, removidos ou alterados. Em seguida, ele usa um algoritmo chamado Eugene W. Myers para descobrir o número mínimo de mudanças necessárias para transformar a lista antiga na nova.

Depois que o DiffUtil descobre o que mudou, o RecyclerView pode usar essas informações para atualizar apenas os itens que foram alterados, adicionados, removidos ou movidos, o que é muito mais eficiente do que refazer a lista inteira.

Nesta tarefa, você vai fazer upgrade do SleepNightAdapter para usar DiffUtil e otimizar o RecyclerView para mudanças nos dados.

Etapa 1: implementar SleepNightDiffCallback

Para usar a funcionalidade da classe DiffUtil, estenda DiffUtil.ItemCallback.

  1. Abra SleepNightAdapter.kt.
  2. Abaixo da definição completa da classe SleepNightAdapter, crie uma nova classe de nível superior chamada SleepNightDiffCallback que estende DiffUtil.ItemCallback. Transmita SleepNight como um parâmetro genérico.
class SleepNightDiffCallback : DiffUtil.ItemCallback<SleepNight>() {
}
  1. Coloque o cursor no nome da classe SleepNightDiffCallback.
  2. Pressione Alt+Enter (Option+Enter no Mac) e selecione Implementar membros.
  3. Na caixa de diálogo exibida, pressione Shift e clique com o botão esquerdo do mouse para selecionar os métodos areItemsTheSame() e areContentsTheSame(). Em seguida, clique em OK.

    Isso gera stubs em SleepNightDiffCallback para os dois métodos, como mostrado abaixo. O DiffUtil usa esses dois métodos para descobrir como a lista e os itens mudaram.
    override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

    override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
  1. Em areItemsTheSame(), substitua o TODO por um código que testa se os dois itens SleepNight transmitidos, oldItem e newItem, são iguais. Se os itens tiverem o mesmo nightId, eles serão iguais. Portanto, retorne true. Caso contrário, retorne false. O DiffUtil usa esse teste para ajudar a descobrir se um item foi adicionado, removido ou movido.
override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
   return oldItem.nightId == newItem.nightId
}
  1. Em areContentsTheSame(), verifique se oldItem e newItem contêm os mesmos dados, ou seja, se são iguais. Essa verificação de igualdade vai verificar todos os campos, porque SleepNight é uma classe de dados. As classes Data definem automaticamente equals e alguns outros métodos para você. Se houver diferenças entre oldItem e newItem, esse código vai informar a DiffUtil que o item foi atualizado.
override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
   return oldItem == newItem
}

É comum usar um RecyclerView para mostrar uma lista que muda. O RecyclerView fornece uma classe de adaptador, ListAdapter, que ajuda a criar um adaptador RecyclerView com suporte de uma lista.

O ListAdapter acompanha a lista e notifica o adaptador quando ela é atualizada.

Etapa 1: mudar o adaptador para estender ListAdapter

  1. No arquivo SleepNightAdapter.kt, mude a assinatura da classe SleepNightAdapter para estender ListAdapter.
  2. Se solicitado, importe androidx.recyclerview.widget.ListAdapter.
  3. Adicione SleepNight como o primeiro argumento ao ListAdapter, antes de SleepNightAdapter.ViewHolder.
  4. Adicione SleepNightDiffCallback() como um parâmetro ao construtor. O ListAdapter vai usar isso para descobrir o que mudou na lista. A assinatura da classe SleepNightAdapter finalizada vai ficar assim:
class SleepNightAdapter : ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
  1. Na classe SleepNightAdapter, exclua o campo data, incluindo o setter. Você não precisa mais dele, porque o ListAdapter acompanha a lista para você.
  2. Exclua a substituição de getItemCount(), porque o ListAdapter implementa esse método para você.
  3. Para se livrar do erro em onBindViewHolder(), mude a variável item. Em vez de usar data para receber um item, chame o método getItem(position) fornecido pelo ListAdapter.
val item = getItem(position)

Etapa 2: use submitList() para manter a lista atualizada

Seu código precisa informar ao ListAdapter quando uma lista alterada estiver disponível. O ListAdapter fornece um método chamado submitList() para informar ao ListAdapter que uma nova versão da lista está disponível. Quando esse método é chamado, o ListAdapter compara a nova lista com a antiga e detecta itens que foram adicionados, removidos, movidos ou alterados. Em seguida, o ListAdapter atualiza os itens mostrados por RecyclerView.

  1. Abra SleepTrackerFragment.kt.
  2. Em onCreateView(), no observador em sleepTrackerViewModel, encontre o erro em que a variável data excluída é referenciada.
  3. Substitua adapter.data = it por uma chamada para adapter.submitList(it). Confira o código atualizado abaixo.

sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
   it?.let {
       adapter.submitList(it)
   }
})
  1. Execute o app. Ele vai rodar mais rápido, talvez não de forma perceptível se a lista for pequena.

Nesta tarefa, você vai usar a mesma técnica dos codelabs anteriores para configurar a vinculação de dados e eliminar as chamadas para findViewById().

Etapa 1: adicionar vinculação de dados ao arquivo de layout

  1. Abra o arquivo de layout list_item_sleep_night.xml na guia Texto.
  2. Coloque o cursor na tag ConstraintLayout e pressione Alt+Enter (Option+Enter em um Mac). O menu de intenções (menu de "correção rápida") é aberto.
  3. Selecione Converter em layout de vinculação de dados. Isso envolve o layout em <layout> e adiciona uma tag <data> dentro.
  4. Role de volta para o topo, se necessário, e dentro da tag <data>, declare uma variável chamada sleep.
  5. Faça com que o type seja o nome totalmente qualificado de SleepNight, com.example.android.trackmysleepquality.database.SleepNight. A tag <data> finalizada vai ficar assim:
   <data>
        <variable
            name="sleep"
            type="com.example.android.trackmysleepquality.database.SleepNight"/>
    </data>
  1. Para forçar a criação do objeto Binding, selecione Build > Clean Project e depois Build > Rebuild Project. Se você ainda tiver problemas, selecione File > Invalidate Caches / Restart. O objeto de vinculação ListItemSleepNightBinding, junto com o código relacionado, é adicionado aos arquivos gerados do projeto.

Etapa 2: inflar o layout do item usando a vinculação de dados

  1. Abra SleepNightAdapter.kt.
  2. Na classe ViewHolder, encontre o método from().
  3. Exclua a declaração da variável view.

Código a ser excluído:

val view = layoutInflater
       .inflate(R.layout.list_item_sleep_night, parent, false)
  1. Onde estava a variável view, defina uma nova variável chamada binding que infla o objeto de vinculação ListItemSleepNightBinding, conforme mostrado abaixo. Faça a importação necessária do objeto de vinculação.
val binding =
ListItemSleepNightBinding.inflate(layoutInflater, parent, false)
  1. No final da função, em vez de retornar o view, retorne binding.
return ViewHolder(binding)
  1. Para se livrar do erro, coloque o cursor na palavra binding. Pressione Alt+Enter (Option+Enter em um Mac) para abrir o menu de intenção.
  1. Selecione Mudar o tipo de parâmetro "itemView" do construtor principal da classe "ViewHolder" para "ListItemSleepNightBinding". Isso atualiza o tipo de parâmetro da classe ViewHolder.

  1. Role para cima até a definição da classe ViewHolder para ver a mudança na assinatura. Você vai encontrar um erro em itemView porque mudou itemView para binding no método from().

    Na definição da classe ViewHolder, clique com o botão direito do mouse em uma das ocorrências de itemView e selecione Refactor > Rename. Mude o nome para binding.
  2. Adicione o prefixo val ao parâmetro do construtor binding para transformá-lo em uma propriedade.
  3. Na chamada para a classe mãe, RecyclerView.ViewHolder, mude o parâmetro de binding para binding.root. É necessário transmitir um View, e binding.root é o ConstraintLayout raiz no layout do item.
  4. A declaração de classe finalizada vai ficar parecida com o código abaixo.
class ViewHolder private constructor(val binding: ListItemSleepNightBinding) : RecyclerView.ViewHolder(binding.root){

Você também vai encontrar um erro nas chamadas para findViewById(), que será corrigido em seguida.

Etapa 3: substituir findViewById()

Agora é possível atualizar as propriedades sleepLength, quality e qualityImage para usar o objeto binding em vez de findViewById().

  1. Mude as inicializações de sleepLength, qualityString e qualityImage para usar as visualizações do objeto binding, conforme mostrado abaixo. Depois disso, o código não vai mais mostrar erros.
val sleepLength: TextView = binding.sleepLength
val quality: TextView = binding.qualityString
val qualityImage: ImageView = binding.qualityImage

Com o objeto de vinculação no lugar, não é mais necessário definir as propriedades sleepLength, quality e qualityImage. O DataBinding armazena em cache as pesquisas, então não é necessário declarar essas propriedades.

  1. Clique com o botão direito do mouse nos nomes das propriedades sleepLength, quality e qualityImage. Selecione Refatorar > Inline ou pressione Control+Command+N (Option+Command+N em um Mac).
  2. Execute o app. Talvez seja necessário limpar e recompilar o projeto se ele tiver erros.

Nesta tarefa, você vai atualizar o app para usar a vinculação de dados com adaptadores de vinculação e definir os dados nas visualizações.

Em um codelab anterior, você usou a classe Transformations para usar LiveData e gerar strings formatadas para mostrar em visualizações de texto. No entanto, se você precisar vincular tipos diferentes ou complexos, forneça adaptadores de vinculação para ajudar a vinculação de dados a usar esses tipos. Os adaptadores de vinculação são adaptadores que pegam seus dados e os adaptam para algo que a vinculação de dados possa usar para vincular uma visualização, como texto ou uma imagem.

Você vai implementar três adaptadores de vinculação, um para a imagem de qualidade e um para cada campo de texto. Em resumo, para declarar um adaptador de vinculação, defina um método que receba um item e uma visualização e adicione a anotação @BindingAdapter. No corpo do método, implemente a transformação. Em Kotlin, você pode escrever um adaptador de vinculação como uma função de extensão na classe de visualização que recebe os dados.

Etapa 1: criar adaptadores de vinculação

Observe que você terá que importar várias classes na etapa, e elas não serão chamadas individualmente.

  1. Abra SleepNightAdapater.kt.
  2. Na classe ViewHolder, encontre o método bind() e lembre-se da função dele. Você vai usar o código que calcula os valores de binding.sleepLength, binding.quality e binding.qualityImage dentro do adaptador. Por enquanto, deixe o código como está. Você vai movê-lo em uma etapa posterior.
  3. No pacote sleeptracker, crie e abra um arquivo chamado BindingUtils.kt.
  4. Declare uma função de extensão em TextView, chamada setSleepDurationFormatted, e transmita uma SleepNight. Essa função será seu adaptador para calcular e formatar a duração do sono.
fun TextView.setSleepDurationFormatted(item: SleepNight) {}
  1. No corpo de setSleepDurationFormatted, vincule os dados à visualização como fez em ViewHolder.bind(). Chame convertDurationToFormatted() e defina o text do TextView como o texto formatado. Como essa é uma função de extensão em TextView, você pode acessar diretamente a propriedade text.
text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, context.resources)
  1. Para informar a vinculação de dados sobre esse adaptador de vinculação, faça a anotação @BindingAdapter na função.
  2. Essa função é o adaptador do atributo sleepDurationFormatted. Portanto, transmita sleepDurationFormatted como um argumento para @BindingAdapter.
@BindingAdapter("sleepDurationFormatted")
  1. O segundo adaptador define a qualidade do sono com base no valor de um objeto SleepNight. Crie uma função de extensão chamada setSleepQualityString() em TextView e transmita uma SleepNight.
  2. No corpo, vincule os dados à visualização como fez em ViewHolder.bind(). Chame convertNumericQualityToString e defina o text.
  3. Anote a função com @BindingAdapter("sleepQualityString").
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight) {
   text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
  1. O terceiro adaptador de vinculação define a imagem em uma visualização de imagem. Crie a função de extensão em ImageView, chame setSleepImage e use o código de ViewHolder.bind(), conforme mostrado abaixo.
@BindingAdapter("sleepImage")
fun ImageView.setSleepImage(item: SleepNight) {
   setImageResource(when (item.sleepQuality) {
       0 -> R.drawable.ic_sleep_0
       1 -> R.drawable.ic_sleep_1
       2 -> R.drawable.ic_sleep_2
       3 -> R.drawable.ic_sleep_3
       4 -> R.drawable.ic_sleep_4
       5 -> R.drawable.ic_sleep_5
       else -> R.drawable.ic_sleep_active
   })
}

Etapa 2: atualizar o SleepNightAdapter

  1. Abra SleepNightAdapter.kt.
  2. Exclua tudo no método bind(), porque agora você pode usar a vinculação de dados e seus novos adaptadores para fazer esse trabalho.
fun bind(item: SleepNight) {
}
  1. Em bind(), atribua sleep a item, porque é necessário informar ao objeto de vinculação sobre seu novo SleepNight.
binding.sleep = item
  1. Abaixo dessa linha, adicione binding.executePendingBindings(). Essa chamada é uma otimização que pede à vinculação de dados para executar imediatamente todas as vinculações pendentes. É sempre recomendável chamar executePendingBindings() ao usar adaptadores de vinculação em um RecyclerView, porque isso pode acelerar um pouco o dimensionamento das visualizações.
 binding.executePendingBindings()

Etapa 3: adicionar vinculações ao layout XML

  1. Abra list_item_sleep_night.xml.
  2. No ImageView, adicione uma propriedade app com o mesmo nome do adaptador de vinculação que define a imagem. Transmita a variável sleep, conforme mostrado abaixo.

    Essa propriedade cria a conexão entre a visualização e o objeto de vinculação pelo adaptador. Sempre que sleepImage for referenciado, o adaptador vai adaptar os dados do SleepNight.
app:sleepImage="@{sleep}"
  1. Faça o mesmo para as visualizações de texto sleep_length e quality_string. Sempre que sleepDurationFormatted ou sleepQualityString forem referenciados, os adaptadores vão adaptar os dados do SleepNight.
app:sleepDurationFormatted="@{sleep}"
app:sleepQualityString="@{sleep}"
  1. Execute o app. Ele vai funcionar exatamente como antes. Os adaptadores de vinculação cuidam de todo o trabalho de formatação e atualização das visualizações à medida que os dados mudam, simplificando o ViewHolder e ao código uma estrutura muito melhor do que antes.

Você mostrou a mesma lista nos últimos exercícios. Isso é proposital para mostrar que a interface Adapter permite arquitetar seu código de várias maneiras diferentes. Quanto mais complexo for o código, mais importante será arquitetá-lo bem. Em apps de produção, esses e outros padrões são usados com RecyclerView. Todos os padrões funcionam, e cada um tem seus benefícios. A escolha depende do que você está criando.

Parabéns! Agora você está no caminho certo para dominar o RecyclerView no Android.

Projeto do Android Studio: RecyclerViewDiffUtilDataBinding.

DiffUtil:

  • O RecyclerView tem uma classe chamada DiffUtil, que calcula as diferenças entre duas listas.
  • O DiffUtil tem uma classe chamada ItemCallBack que você estende para descobrir a diferença entre duas listas.
  • Na classe ItemCallback, substitua os métodos areItemsTheSame() e areContentsTheSame().

ListAdapter:

  • Para ter um gerenciamento de listas sem custo financeiro, use a classe ListAdapter em vez de RecyclerView.Adapter. No entanto, se você usar ListAdapter, vai precisar escrever seu próprio adaptador para outros layouts. Por isso, este codelab mostra como fazer isso.
  • Para abrir o menu de intenção no Android Studio, coloque o cursor em qualquer item de código e pressione Alt+Enter (Option+Enter em um Mac). Esse menu é especialmente útil para refatorar código e criar stubs para implementar métodos. O menu é sensível ao contexto. Por isso, posicione o cursor exatamente para abrir o menu correto.

Vinculação de dados:

  • Use a vinculação de dados no layout do item para vincular dados às visualizações.

Adaptadores de vinculação:

  • Antes, você usou Transformations para criar strings com base em dados. Se você precisar vincular dados de tipos diferentes ou complexos, forneça adaptadores de vinculação para ajudar a vinculação de dados a usá-los.
  • Para declarar um adaptador de vinculação, defina um método que receba um item e uma visualização e anote o método com @BindingAdapter. No Kotlin, é possível escrever o adaptador de vinculação como uma função de extensão no View. Transmita o nome da propriedade que o adaptador adapta. Exemplo:
@BindingAdapter("sleepDurationFormatted")
  • No layout XML, defina uma propriedade app com o mesmo nome do adaptador de vinculação. Transmita uma variável com os dados. Exemplo:
.app:sleepDurationFormatted="@{sleep}"

Cursos da Udacity:

Documentação do desenvolvedor Android:

Outros recursos:

Esta seção lista as possíveis atividades de dever de casa para os alunos que estão fazendo este codelab como parte de um curso ministrado por um professor. Cabe ao professor fazer o seguinte:

  • Atribuir o dever de casa, se necessário.
  • Informar aos alunos como enviar deveres de casa.
  • Atribuir nota aos deveres de casa.

Os professores podem usar essas sugestões o quanto quiserem, podendo passar os exercícios que acharem mais apropriados como dever de casa.

Se você estiver seguindo este codelab por conta própria, sinta-se à vontade para usar esses deveres de casa para testar seu conhecimento.

Responda estas perguntas

Pergunta 1

Quais das seguintes opções são necessárias para usar DiffUtil? Selecione todas as opções aplicáveis.

▢ Estenda a classe ItemCallBack.

▢ Substituir areItemsTheSame().

▢ Substituir areContentsTheSame().

▢ Use a vinculação de dados para rastrear as diferenças entre os itens.

Pergunta 2

Quais das alternativas a seguir são verdadeiras sobre os adaptadores de vinculação?

▢ Um adaptador de vinculação é uma função anotada com @BindingAdapter.

▢ O uso de um adaptador de vinculação permite separar a formatação de dados do armazenador de visualização.

▢ Você precisa usar um RecyclerViewAdapter se quiser usar adaptadores de vinculação.

▢ Os adaptadores de vinculação são uma boa solução quando você precisa transformar dados complexos.

Pergunta 3

Quando você deve usar Transformations em vez de um adaptador de vinculação? Selecione todas as opções aplicáveis.

▢ Seus dados são simples.

▢ Você está formatando uma string.

▢ Sua lista é muito longa.

▢ Seu ViewHolder contém apenas uma visualização.

Comece a próxima lição: 7.3: GridLayout com RecyclerView