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
A maioria dos apps que usam listas e grades para mostrar itens permite que os usuários interajam com eles. Tocar em um item de uma lista e ver os detalhes dele é um caso de uso muito comum para esse tipo de interação. Para isso, adicione listeners de clique que respondam aos toques do usuário em itens mostrando uma visualização de detalhes.
Neste codelab, você vai adicionar interação ao seu RecyclerView
, criando uma versão estendida do app de rastreamento do sono da série anterior de codelabs.
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 objeto de acesso a dados (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 umAdapter
,ViewHolder
e um layout de item. - Como implementar a vinculação de dados para
RecyclerView
. - Como criar e usar adaptadores de vinculação para transformar dados.
- Como usar o
GridLayoutManager
.
O que você vai aprender
- Como tornar clicáveis os itens no
RecyclerView
. Implemente um listener de cliques para navegar até uma visualização de detalhes quando um item for clicado.
Atividades deste laboratório
- Crie uma versão estendida do app TrackMySleepQuality do codelab anterior desta série.
- Adicione um listener de clique à sua lista e comece a ouvir a interação do usuário. Quando um item da lista é tocado, ele aciona a navegação até um fragmento com detalhes sobre o item clicado. O código inicial fornece código para o fragmento de detalhes, bem como o código de navegação.
O app rastreador de sono inicial 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 usa uma arquitetura simplificada com um controlador de interface, um modelo de visualização e LiveData
, além de um banco de dados Room
para manter os dados de sono.
Neste codelab, você vai adicionar a capacidade de responder quando um usuário tocar em um item na grade, o que vai abrir uma tela de detalhes como a abaixo. O código dessa tela (fragmento, modelo de visualização e navegação) é fornecido com o app inicial, e você vai implementar o mecanismo de processamento de cliques.
Etapa 1: instalar o app inicial
- Faça o download do código inicial do RecyclerViewClickHandler (link em inglês) no GitHub e abra o projeto no Android Studio.
- Crie e execute o app inicial de rastreamento do sono.
[Opcional] Atualize o app se quiser usar o app do codelab anterior
Se você vai trabalhar com o app inicial fornecido no GitHub para este codelab, pule para a próxima etapa.
Se você quiser continuar usando o app de monitoramento do sono criado no codelab anterior, siga as instruções abaixo para atualizar o app e incluir o código do fragmento da tela de detalhes.
- Mesmo que você esteja continuando com seu app atual, baixe o código RecyclerViewClickHandler-Starter do GitHub para copiar os arquivos.
- Copie todos os arquivos do 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
, emSleepDatabaseDao
, 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>
- Em
res/values/strings
, adicione o seguinte recurso de string:
<string name="close">Close</string>
- Limpe e recrie o 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 manipulador de cliques que navega até um fragmento com detalhes sobre a noite de sono clicada. O código inicial já contém o fragmento e o gráfico de navegação para essa SleepDetailFragment
, porque é muito código, e fragmentos e navegação não fazem parte deste codelab. Conheça o seguinte código:
- No app, encontre o pacote
sleepdetail
. Esse pacote contém o fragmento, o ViewModel e a fábrica de ViewModel para um fragmento que mostra 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 aoSleepTrackerFragment
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, do modelo de visualização e do observador para navegação. - No pacote
sleepdetail
, abra e inspecione o código doSleepDetailViewModelFactory
. - Na pasta de layout, inspecione
fragment_sleep_detail.xml
. Observe a variávelsleepDetailViewModel
definida na tag<data>
para receber os dados a serem mostrados em cada visualização do modelo de visualização.
O layout contém umConstraintLayout
com umImageView
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 dosleep_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ê vai atualizar o RecyclerView
para responder aos toques do usuário mostrando uma tela de detalhes do item tocado.
Receber e processar cliques é uma tarefa dividida em duas partes: primeiro, você precisa ouvir 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 app?
- O
SleepTrackerFragment
hospeda muitas visualizações. Portanto, ouvir eventos de clique no nível do fragmento não informa qual item foi clicado. Ele não informa se foi um item clicado ou um dos outros elementos da interface. - Ao ouvir no nível
RecyclerView
, é difícil descobrir exatamente em qual item da lista o usuário clicou. - O melhor ritmo para receber informações sobre um item clicado é no objeto
ViewHolder
, já que ele representa um item da lista.
Embora o ViewHolder
seja um ótimo lugar para detectar cliques, geralmente não é o lugar certo para processá-los. Então, qual é o melhor lugar para processar os cliques?
- O
Adapter
mostra 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 processar cliques no
ViewModel
porque ele tem acesso aos dados e à lógica para determinar o que precisa acontecer em resposta ao clique.ViewModel
Etapa 1: criar um listener de clique e acioná-lo no layout do item
- Na pasta
sleeptracker
, abra SleepNightAdapter.kt. - No fim 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 mostra um item da lista é clicada, ela chama essa funçãoonClick()
. Você vai definir a propriedadeandroid:onClick
da visualização mais tarde 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á mostrando, e essas informações precisam ser transmitidas para processar o clique.
class SleepNightListener() {
fun onClick(night: SleepNight) =
}
- Para definir o que
onClick()
faz, forneça um callbackclickListener
no construtor deSleepNightListener
e atribua-o aonClick()
.
Dar um nome à lambda que processa o clique,clickListener
, ajuda a acompanhar a função à medida que ela é transmitida entre as classes. O callbackclickListener
só precisa donight.nightId
para acessar dados do banco de dados. A classeSleepNightListener
finalizada vai ficar assim:
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. Dê ao novo<variable>
umname
declickListener.
. Defina otype
como o nome totalmente qualificado da classecom.example.android.trackmysleepquality.sleeptracker.SleepNightListener
, conforme mostrado abaixo. Agora você pode acessar a funçãoonClick()
emSleepNightListener
usando esse layout.
<variable
name="clickListener"
type="com.example.android.trackmysleepquality.sleeptracker.SleepNightListener" />
- Para detectar cliques em qualquer parte desse item da lista, adicione o atributo
android:onClick
aoConstraintLayout
.
Defina o atributo comoclickListener:onClick(sleep)
usando uma lambda de vinculação de dados, conforme mostrado abaixo:
android:onClick="@{() -> clickListener.onClick(sleep)}"
Etapa 2: transmitir o listener de clique ao holder de visualização e ao objeto de vinculação
- Abra SleepNightAdapter.kt.
- Modifique o construtor da classe
SleepNightAdapter
para receber umval clickListener: SleepNightListener
. Quando o adaptador vincula oViewHolder
, ele precisa fornecer a ele esse listener de clique.
class SleepNightAdapter(val clickListener: SleepNightListener):
ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
- Em
onBindViewHolder()
, atualize a chamada paraholder.bind()
para também transmitir o listener de clique para oViewHolder
. Você vai receber um erro do compilador porque adicionou um parâmetro à chamada de função.
holder.bind(getItem(position)!!, clickListener)
- Adicione o parâmetro
clickListener
abind()
. Para fazer isso, coloque o cursor sobre o erro e pressioneAlt+Enter
(Windows) ouOption+Enter
(Mac), conforme mostrado na captura de tela abaixo.
- Na classe
ViewHolder
, dentro da funçãobind()
, atribua o listener de clique ao objetobinding
. Você vai receber um erro porque precisa atualizar o objeto de vinculação.
binding.clickListener = clickListener
- Para atualizar a vinculação de dados, limpe e recompile seu projeto. Talvez seja necessário invalidar os caches também. Você pegou um listener de clique do construtor do adaptador e o transmitiu até o fixador de visualização e o objeto de vinculação.
Etapa 3: mostrar um toast quando um item for tocado
Agora você tem o código para capturar um clique, mas ainda não implementou o que acontece quando um item da lista é tocado. A resposta mais simples é mostrar um toast com o nightId
quando um item é 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
. Ele mostra um erro porque agora espera um parâmetro de listener de clique. - Defina um listener de clique transmitindo uma lambda para o
SleepNightAdapter
. Essa lambda simples apenas mostra um toast com onightId
, conforme mostrado abaixo. Você precisará importarToast
. Confira abaixo a definição completa e atualizada.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
- Execute o app, toque nos itens e verifique se eles mostram um toast com o
nightId
correto. Como os itens têm valores denightId
cada vez maiores, e o app mostra a noite mais recente primeiro, o item com o menornightId
fica na parte de baixo da lista.
Nesta tarefa, você vai mudar o comportamento quando um item no RecyclerView
é clicado. Em vez de mostrar um toast, o app vai navegar até um fragmento de detalhes que mostra mais informações sobre a noite clicada.
Etapa 1: navegar ao clicar
Nesta etapa, em vez de apenas mostrar um aviso, você vai mudar a lambda do listener de clique em onCreateView()
do SleepTrackerFragment
para transmitir o nightId
ao SleepTrackerViewModel
e acionar a navegação até o SleepDetailFragment
.
Defina a função de manipulador de cliques:
- Abra SleepTrackerViewModel.kt.
- Dentro de
SleepTrackerViewModel
, perto do final, defina a função do gerenciador de cliquesonSleepNightClicked()
.
fun onSleepNightClicked(id: Long) {
}
- Dentro da
onSleepNightClicked()
, acione a navegação definindo_navigateToSleepDetail
como aid
transmitida da noite de sono clicada.
fun onSleepNightClicked(id: Long) {
_navigateToSleepDetail.value = id
}
- implementar
_navigateToSleepDetail
; Como você já fez antes, defina umprivate MutableLiveData
para o estado de navegação. e umval
público para acompanhar.
private val _navigateToSleepDetail = MutableLiveData<Long>()
val navigateToSleepDetail
get() = _navigateToSleepDetail
- Defina o método a ser chamado depois que o app terminar de navegar. Chame-o de
onSleepDetailNavigated()
e defina o valor comonull
.
fun onSleepDetailNavigated() {
_navigateToSleepDetail.value = null
}
Adicione o código para chamar o gerenciador de cliques:
- Abra SleepTrackerFragment.kt e role a tela para baixo até o código que cria o adaptador e define
SleepNightListener
para mostrar um toast.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
- Adicione o seguinte código abaixo do toast para chamar um gerenciador de cliques,
onSleepNighClicked()
, nosleepTrackerViewModel
quando um item for tocado. Transmita onightId
para que o modelo de visualização saiba qual noite de sono precisa ser buscada. Isso gera um erro, já que você ainda não definiuonSleepNightClicked()
. Você pode manter, comentar ou excluir o toast, como quiser.
sleepTrackerViewModel.onSleepNightClicked(nightId)
Adicione o código para observar cliques:
- Abra SleepTrackerFragment.kt.
- Em
onCreateView()
, logo acima da declaração demanager
, adicione o código para observar o novonavigateToSleepDetail
LiveData
. QuandonavigateToSleepDetail
mudar, navegue até oSleepDetailFragment
, transmitindo onight
e chameonSleepDetailNavigated()
depois. Como você já fez isso em um codelab anterior, aqui está 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 os erros. Ele vai mostrar um rastreamento de pilha, incluindo algo como 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 código. O app falha quando você clica em um item, e o único código novo é para processar o clique.
No entanto, com esse novo mecanismo de processamento de cliques, agora é possível que os adaptadores de vinculação sejam chamados com um valor null
para item
. Em particular, quando o app é iniciado, o LiveData
começa como null
. Portanto, é necessário adicionar verificações de nulo a cada um dos adaptadores.
- Em
BindingUtils.kt
, para cada um dos adaptadores de vinculação, mude o tipo do argumentoitem
para anulável e encapsule o corpo comitem?.let{...}
. Por exemplo, seu adaptador parasleepQualityString
vai ficar assim: Mude os outros adaptadores da mesma forma.
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight?) {
item?.let {
text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
}
- Execute o app. Toque em um item para abrir uma visualização detalhada.
Projeto do Android Studio: RecyclerViewClickHandler (link em inglês).
Para fazer com que os itens em um RecyclerView
respondam a cliques, anexe listeners de clique aos itens da lista no ViewHolder
e processe os cliques no ViewModel
.
Para que os itens em um RecyclerView
respondam aos cliques, faça o seguinte:
- Crie uma classe de listener que receba uma lambda e a 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 ao construtor do adaptador, ao holder 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, onde 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 até um fragmento de detalhes.
Curso da Udacity:
Documentação do desenvolvedor Android:
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
Suponha que seu app tenha um RecyclerView
que mostra itens em uma lista de compras. O app também define uma classe de listener de clique:
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 o RecyclerView
que mostra 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
, dentro da 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 um RecyclerView
respondam aos cliques? Selecione todas as opções aplicáveis.
▢ No arquivo de layout que mostra o RecyclerView
, adicione-o a <androidx.recyclerview.widget.RecyclerView>
▢ Adicione-a ao arquivo de layout de um item na 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-a ao arquivo de layout de um item na linha. Se você quiser que um único TextView
no item seja clicável, adicione-o ao <TextView>
.
▢ Sempre adicione-a ao arquivo de layout para a MainActivity
.
Comece a próxima lição: