Este codelab faz parte do curso Conceitos básicos do Kotlin para Android. Você aproveitará mais o curso se fizer os codelabs em sequência. Todos os codelabs do curso estão listados na página de destino dos codelabs do curso Conceitos básicos do Kotlin para Android.
Introdução
A maioria dos apps que usam listas e grades que exibem itens permitem que os usuários interajam com eles. Tocar em um item da lista e ver os detalhes dele é um caso de uso muito comum para esse tipo de interação. Para isso, você pode adicionar listeners de cliques que respondem aos toques do usuário em itens mostrando uma visualização detalhada.
Neste codelab, você adicionará interação ao RecyclerView
, com base em uma versão estendida do app monitor de sono da série anterior de codelabs.
O que você já precisa saber
- Criação de uma interface do usuário básica usando uma atividade, fragmentos e visualizações.
- Navegar entre fragmentos usando
safeArgs
para transmitir dados entre eles. - Veja modelos, visualizações de fábricas, transformações e
LiveData
e os respectivos observadores. - Como criar um banco de dados
Room
, um objeto de acesso a dados (DAO, na sigla em inglês) e definir entidades. - Como usar corrotinas para banco de dados e outras tarefas de longa duração.
- Como implementar um
RecyclerView
básico com umAdapter
,ViewHolder
e o layout do item. - Como implementar a vinculação de dados para a
RecyclerView
. - Como criar e usar adaptadores de vinculação para transformar dados.
- Como usar
GridLayoutManager
.
O que você vai aprender
- Como tornar os itens no
RecyclerView
clicáveis. Implemente um listener de clique para navegar até uma visualização detalhada quando um item for clicado.
Atividades do laboratório
- Crie em uma versão estendida do app TrackMySleepQuality do codelab anterior desta série.
- Adicione um listener de cliques à lista e comece a ouvir a interação do usuário. Quando um item da lista é tocado, ele aciona a navegação para um fragmento com detalhes sobre o item clicado. O código inicial fornece o código para o fragmento de detalhes, bem como o código de navegação.
O app inicial do rastreador de sono tem duas telas, representadas por fragmentos, como mostrado na figura abaixo.
A primeira tela, mostrada à esquerda, tem botões para iniciar e interromper o rastreamento. A tela mostra alguns dados de sono do usuário. O botão Limpar exclui permanentemente todos os dados que o app coletou para o usuário. A segunda tela, mostrada à direita, serve para selecionar uma classificação de qualidade do sono.
Esse app usa uma arquitetura simplificada com um controlador de IU, modelo de visualização e LiveData
, além de um banco de dados Room
para manter os dados de sono.
Neste codelab, você adicionará a capacidade de responder quando um usuário tocar em um item na grade, o que exibirá uma tela de detalhes como a mostrada abaixo. O código dessa tela (fragmento, modelo de visualização e navegação) é fornecido com o app inicial, e você implementará o mecanismo de manipulação de cliques.
Etapa 1: fazer o download do app inicial
- Faça o download do código RecyclerViewClickHandler-Starter no GitHub e abra o projeto no Android Studio.
- Crie e execute o app do rastreador de sono inicial.
[Opcional] Atualize seu app se quiser usá-lo do codelab anterior.
Se você for trabalhar no app inicial fornecido no GitHub para este codelab, pule para a próxima etapa.
Se você quiser continuar usando seu próprio app de monitoramento de sono criado no codelab anterior, siga as instruções abaixo para atualizar o app existente para que ele tenha o código do fragmento da tela de detalhes.
- Mesmo que você continue trabalhando no app, faça o download do código RecyclerViewClickHandler-Starter no GitHub para copiar os arquivos.
- Copie todos os arquivos no pacote
sleepdetail
. - Na pasta
layout
, copie o arquivofragment_sleep_detail.xml
. - Copie o conteúdo atualizado de
navigation.xml
, que adiciona a navegação para osleep_detail_fragment
. - No pacote
database
, noSleepDatabaseDao
, adicione o novo métodogetNightWithId()
:
/**
* Selects and returns the night with given nightId.
*/
@Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
fun getNightWithId(key: Long): LiveData<SleepNight>
- No
res/values/strings
, adicione o seguinte recurso de string:
<string name="close">Close</string>
- Limpe e recrie seu app para atualizar a vinculação de dados.
Etapa 2: inspecionar o código da tela de detalhes do sono
Neste codelab, você vai implementar um gerenciador de cliques que navega para um fragmento que mostra detalhes sobre a noite de sono clicada. O código inicial já contém o fragmento e o gráfico de navegação deste SleepDetailFragment
, porque são bastante códigos, e os fragmentos e a navegação não fazem parte deste codelab. Familiarize-se com o seguinte código:
- No app, localize o pacote
sleepdetail
. Este pacote contém o fragmento, o modelo de visualização e a fábrica de modelo de visualização de um fragmento que exibe detalhes de uma noite de sono. - No pacote
sleepdetail
, abra e inspecione o código doSleepDetailViewModel
. Esse modelo de visualização usa a chave de umSleepNight
e um DAO no construtor.
O corpo da classe tem código para receber oSleepNight
da chave especificada e a variávelnavigateToSleepTracker
para controlar a navegação de volta para oSleepTrackerFragment
quando o botão Fechar é pressionado.
A funçãogetNightWithId()
retorna umLiveData<SleepNight>
e é definida emSleepDatabaseDao
(no pacotedatabase
). - No pacote
sleepdetail
, abra e inspecione o código doSleepDetailFragment
. Observe a configuração da vinculação de dados, o modelo de visualização e o observador para navegação. - No pacote
sleepdetail
, abra e inspecione o código doSleepDetailViewModelFactory
. - Na pasta do layout, inspecione
fragment_sleep_detail.xml
. Observe a variávelsleepDetailViewModel
definida na tag<data>
para que os dados sejam exibidos em cada visualização do modelo de visualização.
O layout contém umConstraintLayout
com umaImageView
para a qualidade do sono, umTextView
para uma classificação de qualidade, umTextView
para a duração do sono e umButton
para fechar o fragmento de detalhes. - Abra o arquivo
navigation.xml
. Para osleep_tracker_fragment
, observe a nova ação parasleep_detail_fragment
.
A nova ação,action_sleep_tracker_fragment_to_sleepDetailFragment
, é a navegação do fragmento do rastreador de sono para a tela de detalhes.
Nesta tarefa, você atualizará o RecyclerView
para responder aos toques do usuário mostrando uma tela de detalhes para o item tocado.
O recebimento e o processamento de cliques são tarefas de duas partes: primeiro, você precisa detectar e receber o clique e determinar qual item foi clicado. Em seguida, responda ao clique com uma ação.
Então, qual é o melhor lugar para adicionar um listener de clique para esse aplicativo?
- O
SleepTrackerFragment
hospeda muitas visualizações. Portanto, a detecção de eventos de clique no nível do fragmento não informará qual item foi clicado. Ele nem saberá se foi um item clicado ou um dos outros elementos da IU. - Ouvir o nível
RecyclerView
, é difícil descobrir exatamente em qual item da lista o usuário clicou. - O melhor ritmo para ver informações sobre um item clicado é no objeto
ViewHolder
, já que ele representa um item da lista.
Embora o ViewHolder
seja um ótimo local para detectar cliques, ele geralmente não é o lugar certo para processá-los. Então, qual é o melhor lugar para gerenciar os cliques?
- O
Adapter
exibe itens de dados em visualizações para que você possa processar os cliques no adaptador. No entanto, a tarefa do adaptador é adaptar os dados para exibição, não lidar com a lógica do app. - Geralmente, você precisa gerenciar cliques no
ViewModel
, porque oViewModel
tem acesso aos dados e à lógica para determinar o que precisa acontecer em resposta ao clique.
Etapa 1: criar um listener de clique e acioná-lo no layout do item
- Na pasta
sleeptracker
, abra SleepNightAdapter.kt. - No final do arquivo, no nível superior, crie uma nova classe de listener,
SleepNightListener
.
class SleepNightListener() {
}
- Na classe
SleepNightListener
, adicione uma funçãoonClick()
. Quando a visualização que exibe um item da lista é clicada, ela chama essa funçãoonClick()
. Você definirá a propriedadeandroid:onClick
da visualização posteriormente para essa função.
class SleepNightListener() {
fun onClick() =
}
- Adicione um argumento de função
night
do tipoSleepNight
aonClick()
. A visualização sabe qual item está sendo exibido e as informações precisam ser transmitidas para processar o clique.
class SleepNightListener() {
fun onClick(night: SleepNight) =
}
- Para definir o que o
onClick()
faz, forneça um callbackclickListener
no construtor daSleepNightListener
e atribua-o àonClick()
.
Atribuir o lambda que gerencia o nome do clique,clickListener
, ajuda a monitorá-lo conforme ele é transmitido entre as classes. O callbackclickListener
só precisa que onight.nightId
acesse dados do banco de dados. A classeSleepNightListener
concluída ficará parecida com o código abaixo.
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
fun onClick(night: SleepNight) = clickListener(night.nightId)
}
- Abra list_item_sleep_night.xml.
- No bloco
data
, adicione uma nova variável para disponibilizar a classeSleepNightListener
usando a vinculação de dados. Defina o novo<variable>
como umname
declickListener.
e defina otype
como o nome totalmente qualificado da classecom.example.android.trackmysleepquality.sleeptracker.SleepNightListener
, conforme mostrado abaixo. Agora você pode acessar a funçãoonClick()
noSleepNightListener
usando esse layout.
<variable
name="clickListener"
type="com.example.android.trackmysleepquality.sleeptracker.SleepNightListener" />
- Para ouvir cliques em qualquer parte desse item da lista, adicione o atributo
android:onClick
aoConstraintLayout
.
Defina o atributo comoclickListener:onClick(sleep)
usando um lambda de vinculação de dados, conforme mostrado abaixo:
android:onClick="@{() -> clickListener.onClick(sleep)}"
Etapa 2: transmita o listener de clique para o fixador de visualização e o objeto de vinculação
- Abra SleepNightAdapter.kt.
- Modifique o construtor da classe
SleepNightAdapter
para receber umval clickListener: SleepNightListener
. Quando o adaptador vincular aViewHolder
, ele precisará fornecê-lo com esse listener de clique.
class SleepNightAdapter(val clickListener: SleepNightListener):
ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
- No
onBindViewHolder()
, atualize a chamada paraholder.bind()
e transmita o listener de clique para oViewHolder
. Você verá um erro do compilador porque adicionou um parâmetro à chamada de função.
holder.bind(getItem(position)!!, clickListener)
- Adicione o parâmetro
clickListener
àbind()
. Para fazer isso, posicione o cursor sobre o erro e pressioneAlt+Enter
(Windows) ouOption+Enter
(Mac) no erro como , conforme a captura de tela abaixo.
- Na classe
ViewHolder
, na funçãobind()
, atribua o listener de clique ao objetobinding
. Você vê um erro porque precisa atualizar o objeto de vinculação.
binding.clickListener = clickListener
- Para atualizar a vinculação de dados, limpe e recrie seu projeto. Talvez você também precise invalidar os caches. Então, você pegou um listener de clique do construtor do adaptador e o passou para o armazenador de visualização e o objeto de vinculação.
Etapa 3: mostrar um aviso quando um item for tocado
Agora você tem o código configurado para capturar um clique, mas não implementou o que acontece quando um item da lista é tocado. A resposta mais simples é exibir um aviso mostrando o nightId
quando um item for clicado. Isso verifica se, quando um item da lista é clicado, o nightId
correto é capturado e transmitido.
- Abra SleepTrackerFragment.kt.
- Em
onCreateView()
, encontre a variáveladapter
. Observe que ele mostra um erro, já que espera um parâmetro de listener de cliques. - Defina um listener de clique transmitindo uma lambda para o
SleepNightAdapter
. Esse lambda simples exibe apenas um aviso mostrando onightId
, conforme mostrado abaixo. Você precisará importarToast
. Veja abaixo a definição atualizada completa.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
- Execute o app, toque em itens e verifique se eles exibem um aviso com o
nightId
correto. Como os itens têm valoresnightId
crescentes e o app exibe a noite mais recente primeiro, o item com o menor valornightId
está na parte inferior da lista.
Nesta tarefa, você mudará o comportamento quando um item na RecyclerView
for clicado, de modo que, em vez de mostrar um aviso, o app navegue até um fragmento de detalhes que mostra mais informações sobre a noite clicada.
Etapa 1: navegar com um clique
Nesta etapa, em vez de apenas exibir um aviso, você mudará o lambda do listener de clique no onCreateView()
do SleepTrackerFragment
para transmitir o nightId
para o SleepTrackerViewModel
e acionar a navegação para o SleepDetailFragment
.
Defina a função do gerenciador de cliques:
- Abra o SleepTrackerViewModel.kt.
- Dentro de
SleepTrackerViewModel
, defina a função de gerenciador de cliquesonSleepNightClicked()
no final.
fun onSleepNightClicked(id: Long) {
}
- Dentro do
onSleepNightClicked()
, acione a navegação definindo_navigateToSleepDetail
como oid
transmitido da noite de sono clicada.
fun onSleepNightClicked(id: Long) {
_navigateToSleepDetail.value = id
}
- implementar
_navigateToSleepDetail
; Como você fez anteriormente, defina umprivate MutableLiveData
para o estado de navegação. E um gettable público doval
para acompanhar.
private val _navigateToSleepDetail = MutableLiveData<Long>()
val navigateToSleepDetail
get() = _navigateToSleepDetail
- Defina o método a ser chamado depois que o app termina de navegar. Dê o nome
onSleepDetailNavigated()
a ela e defina o valor comonull
.
fun onSleepDetailNavigated() {
_navigateToSleepDetail.value = null
}
Adicione o código para chamar o gerenciador de cliques:
- Abra SleepTrackerFragment.kt, role para baixo até o código que cria o adaptador e define
SleepNightListener
para mostrar um aviso.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
- Adicione o seguinte código abaixo da notificação toast para chamar um gerenciador de cliques,
onSleepNighClicked()
, nosleepTrackerViewModel
quando um item for tocado. Passe onightId
para que o modelo de visualização saiba qual noite de sono dormir. Isso gera um erro, porque você ainda não definiuonSleepNightClicked()
. Você pode manter, comentar ou excluir o aviso.
sleepTrackerViewModel.onSleepNightClicked(nightId)
Adicione o código para observar os cliques:
- Abra SleepTrackerFragment.kt.
- No
onCreateView()
, logo acima da declaração domanager
, adicione o código para observar o novoLiveData
donavigateToSleepDetail
. Quando onavigateToSleepDetail
mudar, acesse oSleepDetailFragment
, transmitindo anight
e, em seguida, chame oonSleepDetailNavigated()
. Como você já fez isso em um codelab anterior, este é o código:
sleepTrackerViewModel.navigateToSleepDetail.observe(this, Observer { night ->
night?.let {
this.findNavController().navigate(
SleepTrackerFragmentDirections
.actionSleepTrackerFragmentToSleepDetailFragment(night))
sleepTrackerViewModel.onSleepDetailNavigated()
}
})
- Execute o código, clique em um item e ... o app falha.
Processar valores nulos nos adaptadores de vinculação:
- Execute o app novamente no modo de depuração. Toque em um item e filtre os registros para mostrar erros. Ele mostrará um stack trace incluindo o que está abaixo.
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter item
Infelizmente, o stack trace não deixa claro onde esse erro é acionado. Uma desvantagem da vinculação de dados é que ela pode dificultar a depuração do seu código. O app falha quando você clica em um item e o único código novo é para processar o clique.
No entanto, esse novo mecanismo de gerenciamento de cliques permite que os adaptadores de vinculação sejam chamados com um valor null
para item
. Em particular, quando o app é iniciado, a LiveData
é iniciada como null
. Portanto, é necessário adicionar verificações de nulidade a cada um dos adaptadores.
- Em
BindingUtils.kt
, para cada adaptador de vinculação, mude o tipo de argumentoitem
para anulável e envolva o corpo comitem?.let{...}
. Por exemplo, o adaptador para asleepQualityString
ficará assim. Mude os outros adaptadores da mesma maneira.
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight?) {
item?.let {
text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
}
- Execute o app. Toque em um item para abrir a visualização de detalhes.
Projeto do Android Studio: RecyclerViewClickHandler.
Para fazer com que os itens em uma RecyclerView
respondam a cliques, anexe listeners de clique para listar itens no ViewHolder
e processar cliques no ViewModel
.
Para fazer com que os itens em uma RecyclerView
respondam a cliques, é necessário fazer o seguinte:
- Crie uma classe de listener que use um lambda e o atribua a uma função
onClick()
.
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
fun onClick(night: SleepNight) = clickListener(night.nightId)
}
- Defina o listener de clique na visualização.
android:onClick="@{() -> clickListener.onClick(sleep)}"
- Transmita o listener de clique para o construtor do adaptador no armazenador de visualização e adicione-o ao objeto de vinculação.
class SleepNightAdapter(val clickListener: SleepNightListener):
ListAdapter<DataItem, RecyclerView.ViewHolder>(SleepNightDiffCallback()
holder.bind(getItem(position)!!, clickListener)
binding.clickListener = clickListener
- No fragmento que mostra a visualização de reciclagem, em que você cria o adaptador, defina um listener de clique transmitindo uma lambda para o adaptador.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
sleepTrackerViewModel.onSleepNightClicked(nightId)
})
- Implemente o gerenciador de cliques no modelo de visualização. Para cliques em itens da lista, isso geralmente aciona a navegação para um fragmento de detalhes.
Curso da Udacity:
Documentação do desenvolvedor Android:
Esta seção lista as possíveis atividades para os alunos que estão trabalhando neste codelab como parte de um curso ministrado por um instrutor. Cabe ao instrutor fazer o seguinte:
- Se necessário, atribua o dever de casa.
- Informe aos alunos como enviar o dever de casa.
- Atribua nota aos trabalhos de casa.
Os professores podem usar essas sugestões o quanto quiserem, e eles devem se sentir à vontade para passar o dever de casa como achar adequado.
Se você estiver fazendo este codelab por conta própria, use essas atividades para testar seu conhecimento.
Responda a estas perguntas
Pergunta 1
Suponha que seu app tenha um RecyclerView
que exibe itens em uma lista de compras. O app também define uma classe de listener de cliques:
class ShoppingListItemListener(val clickListener: (itemId: Long) -> Unit) {
fun onClick(cartItem: CartItem) = clickListener(cartItem.itemId)
}
Como você disponibiliza o ShoppingListItemListener
para a vinculação de dados? Escolha uma opção.
▢ No arquivo de layout que contém a RecyclerView
que exibe a lista de compras, adicione uma variável <data>
para ShoppingListItemListener
.
▢ No arquivo de layout que define o layout de uma única linha na lista de compras, adicione uma variável <data>
para ShoppingListItemListener
.
▢ Na classe ShoppingListItemListener
, adicione uma função para ativar a vinculação de dados:
fun onBinding (cartItem: CartItem) {dataBindingEnable(true)}
▢ Na classe ShoppingListItemListener
, na função onClick()
, adicione uma chamada para ativar a vinculação de dados:
fun onClick(cartItem: CartItem) = {
clickListener(cartItem.itemId)
dataBindingEnable(true)
}
Pergunta 2
Onde você adiciona o atributo android:onClick
para que os itens em uma RecyclerView
respondam aos cliques? Selecione todas as opções aplicáveis.
▢ No arquivo de layout que exibe RecyclerView
, adicione-o a <androidx.recyclerview.widget.RecyclerView>
.
▢ Adicione-o ao arquivo de layout de um item da linha. Se você quiser que o item inteiro seja clicável, adicione-a à visualização mãe que contém os itens na linha.
▢ Adicione-o ao arquivo de layout de um item da linha. Se você quiser que um único TextView
no item seja clicável, adicione-o à <TextView>
.
▢ Sempre adicione-o ao arquivo de layout da MainActivity
.
Inicie a próxima lição: