Você está na página 1de 31

PROGRAMAÇÃO IV

AULA 6

Prof. Ricardo Rodrigues Lecheta


CONVERSA INICIAL

Olá, seja bem-vindo a mais uma aula!


Nós já aprendemos como criar telas com formulários e estudamos o ciclo
de vida de uma activity. Nesta aula, vamos estudar sobre listas, que com certeza
é o componente mais utilizado em aplicativos.
Imagine que você precisa organizar suas tarefas do que fazer durante a
semana e decidiu criar um aplicativo para ajudá-lo. Nesta aula, vamos criar um
esboço deste aplicativo para salvar tarefas e mostrá-las em uma lista.
Não vamos usar banco de dados para simplificar o exemplo, até porque o
objetivo da aula é estudar listas. Todas as tarefas serão salvas em memória com
uma HashTable, e os dados serão perdidos ao reiniciar o aplicativo.
Aproveitando: web services e banco de dados vamos estudar em aulas práticas.

TEMA 1 – LISTAS: CONFIGURAÇÃO

Para iniciar, crie um novo projeto chamado HelloTarefas da mesma forma


que fizemos nos outros exemplos. Lembre-se de selecionar o template Empty
Activity.
Logo depois de criar o projeto, adicione as seguintes dependências no
arquivo app/build.gradle. No final do arquivo, você vai encontrar uma tag
dependencies, na qual vão existir outras dependências previamente
configuradas. Basta adicionar essas duas linhas. A lib 'material' contém classes
do Material Design que vamos utilizar e a lib 'recyclerview' é para adicionar a
classe RecyclerView que representa a view de uma lista.

● app/build.gradle

dependencies {
...
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
}

Na figura a seguir, podemos ver o arquivo app/build.gradle aberto no


editor e o local onde as dependências foram adicionadas. Observe que no canto
direito superior da figura, existe um botão Sync now, o qual vai fazer o download
e instalação dessas dependências (libs).
Observação: caso não esteja acostumado com o termo dependência, é
um sinônimo utilizado para biblioteca. Na prática, uma dependência é uma
biblioteca com vários arquivos que é baixada e instalada no projeto.

Figura 1 – Dependência

Feita essa configuração, podemos abrir o arquivo activity_main.xml e


adicionar a view RecyclerView no layout. Observe que deixamos a tag
LinearLayout como raiz do layout.
Algo importante que você precisa se acostumar é que as views
LinearLayout, FrameLayout, TextView, EditText, Button etc. são nativas do
Android e, no nome da tag XML, precisamos colocar apenas o nome da classe
dessa view. Como a classe/view RecyclerView foi adicionada pela dependência
que colocamos no arquivo app/build.gradle, ao declarar a tag XML, precisamos
colocar o nome completo da classe, incluindo o seu pacote. Observe também
que foi dado o id @+id/recyclerView para o RecyclerView.

● activity_main.xml

<?xml version="1.0" encoding="utf-8"?>


<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Pronto, feito isso execute o projeto novamente para validar se as
configurações foram feitas com sucesso. Se tudo funcionar, você verá uma tela
vazia, pois o RecyclerView é uma lista e, por enquanto, está vazia.

Figura 2 – Lista de tarefas vazia

Crédito: Brostock/Shutterstock.

TEMA 2 – LISTAS: EXEMPLO BÁSICO

Para que você consiga visualizar o nosso objetivo, vamos criar uma lista
com três views, conforme mostra a próxima figura.

Figura 3 – Lista de tarefas

Crédito: Brostock/Shutterstock.
Seguindo o mesmo raciocínio, para facilitar o seu entendimento de quais
arquivos vamos criar neste exercício e do local que você precisa criar os
arquivos, veja a figura a seguir.

Figura 4 – Arquivos que vamos criar no projeto

Para começar, vamos criar a classe Tarefa, que vai representar a


entidade que vamos querer salvar. Sempre que for criar uma classe, utilize o
wizard > New > Kotlin File/Class. A classe Tarefa contém apenas dois atributos
(id e título) e também implementa a interface Serializable.

package com.example.hellotarefas

import java.io.Serializable

data class Tarefa(


var id: Int = 0,
var titulo: String = ""

) : Serializable

Nota: é comum utilizarmos o termo entidade para referenciar uma classe


de domínio que é mapeada para uma tabela do banco de dados, ou seja, ela
será salva em uma tabela. Geralmente, essas classes contêm atributos com os
mesmos nomes da tabela do banco de dados. Mais uma vez, em nosso exemplo,
não usaremos banco de dados, pois o objetivo desta aula é aprendermos a
utilizar o RecyclerView, que é a view que cria uma lista.
Com a classe Tarefa criada, precisamos de uma classe que vai
encapsular a lógica para buscar as tarefas e retornar uma lista do tipo
List<Tarefa>. Portanto, crie a classe TarefaService.

package com.example.hellotarefas

object TarefaService {

private val tarefas = mutableListOf<Tarefa>()

init {
for (i in 1..3) {
tarefas.add(Tarefa(i,"Tarefa $i"))
}
}

fun getTarefas(): List<Tarefa> {


return tarefas
}
}

Mais uma vez, explicando um pouco sobre arquitetura de código, é uma


boa prática criar esse tipo de classe para encapsular essa lógica. Atualmente,
como você pode ver, estamos criando uma lista fixa com três tarefas apenas
para brincar e estudar como criar listas com o RecyclerView. Mas, futuramente,
essa classe poderia conter uma lógica para ler essas tarefas do banco de dados
do celular Android, ou até mesmo buscar as tarefas na internet usando um web
service (API). Mas, por enquanto, essa classe já vai criar uma lista com três
tarefas e é suficiente para nossos estudos. Se quiser, altere o loop for, para criar
quantas tarefas achar necessário.
Uma vez que já temos as classes Tarefa e TarefaService criadas,
precisamos ter uma maneira de pegar a lista de tarefas e preencher lá naquele
RecyclerView que está no arquivo activity_main.xml. Para fazer essa ponte
entre a lista de tarefas List<Tarefa> e o RecyclerView, precisamos criar uma
classe chamada de Adapter, que assim como a activity, é uma dupla de uma
classe + layout XML.
Essa parte, aliás, já adianto que é meio chatinha e difícil de entender no
início, portanto, recomendo que depois que fizer o exemplo funcionar, leia e
releia tudo novamente para conseguir encaixar todos os pedaços do quebra-
cabeça.
Uma dica é você imaginar que o RecyclerView é apenas o componente
da lista, mas ele não sabe qual o layout que cada célula da lista vai ter. Lembra
que na figura anterior tinha três tarefas na lista? Cada linha da lista é uma 'célula'
que possui seu próprio layout XML, a qual o Android chama de Adapter.
Para você ir visualizando na prática, podemos dizer que uma prévia do
código da activity que vai preencher a lista de tarefas no RecyclerView é assim:

package com.example.hellotarefas

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}

override fun onResume() {


super.onResume()

// (1) Busca a lista de tarefas


val tarefas = TarefaService.getTarefas()

// (2) Configura o layout do RecyclerView para Vertical


recyclerView.layoutManager = LinearLayoutManager(this)

// (3) Configura o adapter


recyclerView.adapter = TarefaAdapter(tarefas)
}
}

A parte vermelha onde está a classe TarefaAdapter é o código que está


faltando para o exemplo compilar, mas logo vamos ver isso. Por enquanto, segue
explicações de cada parte do código:

1. É uma boa prática deixar a lógica de inicialização da tela no método


onResume() da activity. Nele, é feita a busca da lista de tarefas que
retorna um objeto do tipo List<Tarefa>.
2. O RecyclerView pode ter um layout de listas ou grid (várias colunas). Essa
linha configura o template padrão de listas na vertical.
3. O RecyclerView possui uma propriedade adapter que deve receber uma
classe filha de RecyclerView.Adapter. É essa parte do código que está
faltando para compilar.

Agora, vamos falar da classe Adapter. Essa classe contém a lógica para
preencher o conteúdo de cada célula, ou seja, preencher o conteúdo de cada
linha da lista. O adapter também é uma dupla formada pela classe Kotlin ou Java,
e o layout XML que será utilizado na célula.
Lembra da figura na qual mostramos os arquivos que vamos criar neste
exemplo? Veja-a novamente e perceba o local da classe TarefaAdapter e seu
layout XML adapter_tarefa.xml.
Dito isso, vamos criar o arquivo XML de layout para o adapter. Para isso,
utilize o wizard > New > File e digite '.xml' na extensão ao criar o arquivo. Como
padrão, os arquivos utilizados como Adapter (célula de uma lista) começam com
a palavra 'adapter_', portanto, o nome do arquivo é adapter_tarefa.xml.

● /res/layout/adapter_tarefa.xml

<?xml version="1.0" encoding="utf-8"?>


<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">

<TextView
android:id="@+id/tId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp" />

<TextView
android:id="@+id/tTitulo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp" />
</LinearLayout>

O layout do nosso exemplo é bem simples e contém um TextView onde


vamos mostrar o ID (identificador) da tarefa, que no nosso caso será o número
dela, como 1, 2, ou 3. O outro TextView é para mostrar o título da tarefa. Como
podemos ver, o LinearLayout raiz foi configurado como vertical, assim, os textos
vão ficar um embaixo do outro, conforme a figura com o layout da lista que
mostramos anteriormente.
Essa parte foi fácil, agora vamos para o código da classe do adapter. Para
facilitar a explicação, o código está comentado com algumas cores. A classe
TarefaAdapter recebe como parâmetro no construtor a lista de tarefas
(List<Tarefa>) e é filha de RecyclerView.Adapter<TarefasViewHolder>. A classe
TarefasViewHolder está declarada lá em baixo no mesmo arquivo. Precisamos
criá-la apenas para fechar o contrato com o adapter, e dentro dela ficará
armazenado a view da célula, que é o layout XML do adapter.

package br.com.livroandroid.tarefas.adapter

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.hellotarefas.Tarefa
import com.example.hellotarefas.R
import kotlinx.android.synthetic.main.adapter_tarefa.view.*

// Define o construtor que recebe a lista de tarefas


class TarefaAdapter(val tarefas: List<Tarefa>) :
RecyclerView.Adapter<TarefasViewHolder>() {

// (1) Retorna a quantidade de tarefas na lista


override fun getItemCount(): Int {
return this.tarefas.size
}
// (2) Infla o layout do adapter e retorna o ViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
TarefasViewHolder {
// Infla a view do adapter
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.adapter_tarefa, parent,
false)
val holder = TarefasViewHolder(view)
return holder
}
// (3) Faz o bind para atualizar o valor das views com os dados do
Tarefa
override fun onBindViewHolder(holder: TarefasViewHolder, position:
Int) {
// Recupera o objeto
val tarefa = tarefas[position]
// Recupera a view da linha
val view = holder.itemView
// Atualiza os dados nas views
view.tId.text = tarefa.id.toString()
view.tTitulo.text = tarefa.titulo
}
}

// Subclasse obrigatória de RecyclerView.ViewHolder


// Ela é usada como tipo genérico ao criar a classe de adapter
class TarefasViewHolder(view: View) : RecyclerView.ViewHolder(view)

No código, você pode ver três métodos marcados em laranja. Esses


métodos são obrigatórios e temos a explicação deles a seguir:

1. getItemCount(): este método deve retornar a quantidade de linhas da


lista, ou seja, do RecyclerView. A implementação desse método é simples
e, geralmente, ele apenas retorna a quantidade de elementos contidos no
array, ou lista de objetos, que foi passado como parâmetro para o
construtor da classe do Adapter. No nosso caso, a lista contém três
elementos.

override fun getItemCount(): Int {


return this.tarefas.size
}

2. onCreateViewHolder(): este método deve inflar o layout XML do adapter,


que é aquele adapter_tarefa.xml. No Android, o termo inflar um XML
significa converter este XML para um objeto View. No código, esta linha
faz justamente isso:

val view = inflater.inflate(R.layout.adapter_tarefa, parent, false)

Logo depois de inflar o XML da célula, é criado o objeto ViewHolder.


Holder, em inglês, significa segurar, manter ou conter. Se traduzirmos,
ViewHolder significa o objeto que "contém a view" desta célula. Veja, então, que
ao criar o TarefasViewHolder, é passado no seu construtor o objeto view, que
é o adapter_tarefa.xml convertido para objeto View.

val holder = TarefasViewHolder(view)

3. onBindViewHolder(): este é o último método que temos que implementar.


A ideia é que o Android chame os dois primeiros métodos apenas uma
vez, mas vai chamar o método onBindViewHolder() para o total de
elementos na lista, que no nosso exemplo são três tarefas.

Seguindo o raciocínio de traduzir o inglês para facilitar a explicação, a


palavra bind significa ligação, no sentido de interligar alguma coisa a outra.
Então, esse método onBindViewHolder() é usado para fazer a ligação entre o
objeto Tarefa e o layout XML desta célula, lembrando que o ViewHolder é o
objeto que “contém” este layout XML. Complicado, não é? Na prática, esse
método vai recuperar o objeto Tarefa e vai configurar no XML da célula os
valores desta tarefa:

override fun onBindViewHolder(holder: TarefasViewHolder, position:


Int) {
// Recupera o objeto
val tarefa = tarefas[position]
// Recupera a view da linha
val view = holder.itemView
// Atualiza os dados nas views
view.tId.text = tarefa.id.toString()
view.tTitulo.text = tarefa.titulo
}

Pronto! Com a classe de adapter criada, vamos atualizar o código da


MainActivity. Veja que a lista de tarefas é passada para o construtor do adapter,
pois ele saberá retornar aquele XML para cada célula da lista.

package com.example.hellotarefas

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import br.com.livroandroid.tarefas.adapter.TarefaAdapter
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}

override fun onResume() {


super.onResume()

val tarefas = TarefaService.getTarefas()


recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = TarefaAdapter(tarefas)
}
}

Após fazer todas essas configurações, execute o projeto novamente no


emulador e o resultado será a lista com as três tarefas, conforme mostramos
anteriormente.
Bom, essa foi a parte mais difícil. Os próximos exercícios serão mais
fáceis. Até recomendamos que você prossega com todo o conteúdo da aula e
depois fazça uma boa revisão sobre a classe do Adapter.
Caso tenha achado difícil fazer a classe adapter, saiba que mesmo
desenvolvedores experientes em Android utilizam a técnica do Ctrl+C + Ctrl+V
para criar adapters, ou seja, depois que você faz um, simplesmente copia e,
assim, para os próximos, vai apenas trocando os nomes. O adapter é aquele
template que não muda, e logo você se acostuma.
TEMA 3 – CARDVIEW + EVENTOS

Para deixarmos o layout da lista de notas mais bonito, vamos envolver o


layout do adapter com a classe CardView que faz parte da biblioteca do Material
Design.
Altere o código do do layout do adapter conforme mostrado a seguir:

● /res/layout/adapter_tarefa.xml

<?xml version="1.0" encoding="utf-8"?>


<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:foreground="?attr/selectableItemBackground">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/tId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp" />
<TextView
android:id="@+id/tTitulo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp" />
</LinearLayout>
</androidx.cardview.widget.CardView>

O CardView é um gerenciador de layout muito simples que cria o efeito de


um cartão (card), deixando aquelas bordas no layout, algo que é muito comum
hoje em dia nos aplicativos.
Figura 5 – Lista de tarefas com card

Crédito: Brostock/Shutterstock.

3.1 Tratamento de eventos na lista

Logo depois de criar a lista, com certeza o próximo passo é tratar os


eventos de clique em cada célula. Para isso, temos que adicionar esta linha lá
na view que é inflada do layout XML.

view.setOnClickListener { }

Esta linha podemos inserir lá no método onBindViewHolder() da classe


do Adapter, conforme demonstrado abaixo:

● TarefaAdapter.kt

class TarefaAdapter(val tarefas: List<Tarefa>) . . . {


...
override fun onBindViewHolder(holder: TarefasViewHolder, position: Int) {
...
view.tId.text = tarefa.id.toString()
view.tTitulo.text = tarefa.titulo
...
view.setOnClickListener { }
}
}
Conforme já explicamos algumas vezes, o código que fica entre as chaves
{ } é uma lambda, e por enquanto vamos deixar vazio. Ao executar o projeto
novamente no emulador, você vai reparar que ao clicar em alguma célula, ela
ficará selecionada, conforme mostra a próxima figura.

Figura 6 – Célula selecionada ao clicar no item da lista

Crédito: Brostock/Shutterstock.

Com o evento de clique devidamente configurado na célula, vamos


adicionar uma função de callback chamada onClick no construtor da classe do
Adapter. Essa função vai receber como parâmetro o objeto Tarefa que foi clicado
e não vai retornar nada, portanto, é Unit (void).

val onClick: (Tarefa) -> Unit

Faça a seguinte alteração no código. TarefaAdapter.kt

class TarefaAdapter(val tarefas: List<Tarefa>,


val onClick: (Tarefa) -> Unit) :
RecyclerView.Adapter<TarefasViewHolder>() {
...
override fun onBindViewHolder(holder: TarefasViewHolder, position: Int) {
...
view.tId.text = tarefa.id.toString()
view.tTitulo.text = tarefa.titulo
...
view.setOnClickListener { onClick(tarefa) }
}
}

Pronto! Dentro do view.setOnClickListener { }, a função de callback será


chamada passando como parâmetro a Tarefa. Isso que acabamos de fazer
mostra como passar como parâmetro uma função, que é uma característica de
linguagens de programação funcionais, paradigma que o Kotlin também
implementa.
Para finalizar o exemplo do tratamento de eventos, vamos passar como
parâmetro uma função lambda para a classe TarefaAdapter.

● MainActivity

package com.example.hellotarefas

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import br.com.livroandroid.tarefas.adapter.TarefaAdapter
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}

override fun onResume() {


super.onResume()

val tarefas = TarefaService.getTarefas()


recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = TarefaAdapter(tarefas) {
onClickTarefa(it)
}
}

private fun onClickTarefa(t: Tarefa) {


Toast.makeText(this,"Tarefa: ${t.titulo}",
Toast.LENGTH_SHORT).show()
}
}

Pronto. Feito isso, ao clicar em uma célula da lista, o método


onClickTarefa(tarefa) será chamado. No entanto, observe que há uma
novidade: o Toast.
Utilizar o Toast não requer prática nem habilidade, pois ele apenas mostra
um breve alerta para o usuário. No Android, o Toast é aquele alerta rápido com
um formato de balão, e que fica visível por poucos segundos. Ao criar o Toast,
você pode informar o tempo com as constantes LENGTH_SHORT (2 segundos)
ou LENGTH_LONG (5 segundos)

Figura 7 – Alerta com Toast

Crédito: Brostock/Shutterstock.

Apesar de já termos estudado lambdas, vamos revisar rapidamente como


passar funções como parâmetros. Como a classe TarefaAdapter recebe uma
lista de objetos do tipo Tarefa e a função, podemos passar diretamente a função
como parâmetro com a seguinte sintaxe:

recyclerView.adapter = TarefaAdapter(tarefas, ::onClickTarefa)

Naturalmente, a função onClickTarefa(tarefa) precisa existir e ter os


mesmos parâmetros esperados, que neste caso é o objeto Tarefa.
Outra maneira é usando a sintaxe da lambda, abrindo e fechando chaves
{}.

recyclerView.adapter = TarefaAdapter(tarefas, {
tarefa -> onClickTarefa(tarefa)
})
Neste caso, aquela variável tarefa seguida da seta pode ser omitida, pois
no Kotlin podemos usar o parâmetro it, que é implícito. Naturalmente, a variável
it só pode ser usado se a função possui apenas um único parâmetro, que é este
caso.

recyclerView.adapter = TarefaAdapter(tarefas, {
onClickTarefa(it)
})

Por fim, veja no código anterior que a lambda ficou dentro dos parênteses,
pois ela é um argumento como outro qualquer que é enviado no construtor da
classe TarefaAdapter. Mas, como também já estudamos, sempre que a lambda
for o último argumento, podemos abrir e fechar as chaves depois dos parênteses
com esta sintaxe:

recyclerView.adapter = TarefaAdapter(tarefas) {
onClickTarefa(it)
}

Concluindo a explicação, esta última sintaxe é a mais adotada nos


aplicativos e também é muito comum em outras linguagens.

3.2 Criando uma extensão para o Toast

Ao explicarmos, gostamos muito de passar nossa linha de raciocínio para


você e algo que sempre temos em mente é não digitar códigos chatos e que são
muito repetitivos. Aquele código para criar um Toast é um que com certeza
merece ser abreviado.
Lembra-se de quando criamos uma extensão para a activity para mostrar
um alerta? Copie o arquivo Activity-Extensions.kt que fizemos naquele outro
exemplo e adicione o método toast(). O método vai receber a mensagem que
deve ser exibida no alerta e o parâmetro 'duration' opcional, que por padrão
possui o valor da constante Toast.LENGTH_SHORT.

● Activity-Extensions.kt

package com.example.hellotarefas.extensions

import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity

fun AppCompatActivity.alert(msg: String, callback: () -> Unit


= {}) {
. . .
}

fun AppCompatActivity.toast(msg: String, duration: Int =


Toast.LENGTH_SHORT) {
Toast.makeText(this,msg, duration).show()
}

Pronto! Agora, para fazer um toast, fica bem simples. Lembre-se que para
utilizar extensões, precisamos fazer o import, mas isso o Android Studio sempre
vai nos ajudar.

package com.example.hellotarefas

import com.example.hellotarefas.extensions.toast

class MainActivity : AppCompatActivity() {


...
private fun onClickTarefa(t: Tarefa) {
toast("Tarefa: ${t.titulo}")
}
}

TEMA 4 – TELA DE DETALHES DA TAREFA

Já aprendemos como criar a lista de tarefas, e agora vamos partir para a


segunda parte do nosso exercício, que será implementar as seguintes
funcionalidades:

1. Ao clicar em uma tarefa, vamos abrir a tela de detalhes da tarefa;


2. A tela da tarefa poderá ser utilizada para criar uma nova tarefa ou editar
uma que já existe;
3. Vamos adicionar um botão para criar uma nova tarefa na lista. Esse botão
é aquele (+) na parte inferior direita da lista.
4. Na tela de detalhes da tarefa, vamos adicionar um botão para remover a
tarefa. Esse botão é aquele (X) na AppBar.

Esta figura mostra o exercício completo para você já ir visualizando o nosso


objetivo.
Figura 8 – Cadastro de tarefas

Crédito: Brostock/Shutterstock.

4.1 Tela da tarefa

O primeiro passo para criar o cadastro de tarefas é criar a tela que possui
o formulário para inserir ou editar uma tarefa. Essa tela, como você já sabe, será
uma nova activity, que vamos chamar de TarefaActivity.
Crie essa activity com o wizard > New > Activity > Empty Activity. Ao
utilizar o wizard, a activity será configurada automaticamente no arquivo de
manifesto:

<activity android:name=".TarefaActivity" />

Feito isso, vamos criar o formulário que vai conter o título da tarefa. No
formulário, não vamos mostrar o id da tarefa, ele ficará escondido para simular
o id do banco de dados.

● /res/layout/activity_tarefa.xml

<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Título" />
<EditText
android:id="@+id/tTitulo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<Button
android:id="@+id/btSalvar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Salvar" />
</LinearLayout>

Por fim, vamos criar o código da classe TarefaActivity. Por enquanto, o


botão Salvar ainda não fará nada. Neste momento, no método onCreate(),
vamos ler o objeto Tarefa que será passado como parâmetro e estamos
chamando o método setTarefa(t) para atualizar o formulário com os dados deste
objeto.

● TarefaActivity

package com.example.hellotarefas

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_tarefa.*

class TarefaActivity : AppCompatActivity() {


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tarefa)

// Configura botão voltar e o título


supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.title = "Tarefa"

// Recebe o objeto passado como parâmetro


val tarefa = intent.getSerializableExtra("tarefa") as
Tarefa?

// Atualiza a tela com os dados da tarefa


setTarefa(tarefa)
}

private fun setTarefa(t: Tarefa?) {


if(t != null) {
tTitulo.setText(t.titulo)
}
}
}
Lembrando que nosso objetivo é que, ao clicar em alguma tarefa na lista,
vamos abrir essa tela de detalhes e o objeto tarefa será passado como
parâmetro. Por isso, quando criamos a classe Tarefa, fizemos ela implementar
a interface Serializable.

package com.example.hellotarefas

import java.io.Serializable

data class Tarefa(


var id: Int = 0,
var titulo: String = ""

) : Serializable

Observe que ao ler o parâmetro tarefa da intent, foi feito o casto para
Tarefa? com a interrogação, o que indica que este objeto pode ser nulo.

val tarefa = intent.getSerializableExtra("tarefa") as Tarefa?

Pela regra deste aplicativo fictício, a tela com o formulário pode ser
chamada em duas situações:

1. Na tela inicial, o usuário clicou em alguma tarefa da lista. Neste caso, o


app vai abrir a TarefaActivity passando como parâmetro o objeto tarefa.
2. Na tela inicial, o usuário clicou no botão (+) para criar uma nova tarefa.
Neste caso, o parâmetro tarefa estará nulo e por isso o objeto Tarefa?
foi declarado com a interrogação. Na lógica da activity, no método
setTarefa(t), precisamos verificar se a tarefa existe antes de mostrar os
seus dados no formulário.

Algo interessante deste exemplo e que ainda não tínhamos visto está lá
no método onCreate() da activity:

// Configura botão voltar e o título


supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.title = "Tarefa"

A primeira linha está habilitando o botão de voltar na App Bar (aquele do


canto superior esquerdo), porém, o botão voltar ainda não vai funcionar, e vamos
ver isso no próximo exemplo. A segunda linha está configurando o título na App
Bar.
Obsservação: você vai encontrar exemplos falando de Action Bar e
outros de App Bar, mas podemos dizer que são sinônimos.

4.1 Voltar para a tela anterior

Até o momento, já podemos clicar em alguma tarefa na lista e abrir a tela


de detalhes, mas o botão de voltar da App Bar ainda não funciona. Apenas para
deixar bem claro, temos duas maneiras de mostrar o botão Voltar na App Bar:

1. Adicionar o atributo parentActivityName no arquivo de manifesto, como


fizemos no projeto em que criamos layouts para as telas de login, cadastro
e esqueci senha. Neste caso, o Android implementa a ação de voltar
automaticamente.
2. Utilizar o método setDisplayHomeAsUpEnabled(true) da classe
ActionBar. Neste caso, ainda temos que implementar o método
onOptionsItemSelected(item) na activity (a fazer ainda).

O primeiro jeito de fazer é mais simples, pois basta configurar o arquivo


de manifesto, expliquei dessa maneira antes. Mas, ao fazer isso, o Android vai
fechar todas as activities e vai dar um startActivity novamente na activity definida
lá no atributo parentActivityName. Ou seja, a tela será recriada e pode perder
o seu estado.
Para manter o estado o desejado é que o botão Voltar da App Bar (lá de
cima) tem o mesmo efeito do botão voltar nativo do Android (lá de baixo), que é
apenas fechar a tela, desempilhando essa activity da pilha de atividades (activity
stack).
O código a seguir mostra como implementar o método
onOptionsItemSelected(item), o qual é chamado sempre que um botão da App
Bar é clicado, independentemente se é o botão de Voltar ou outra ação.

class TarefaActivity : AppCompatActivity() {


...

override fun onOptionsItemSelected(item: MenuItem?): Boolean {


when (item?.itemId) {
android.R.id.home -> {
finish()
}
}
return super.onOptionsItemSelected(item)
}
}

Neste método, geralmente é feita uma comparação para ver o id do item


de menu que foi clicado. Por padrão, o id do botão voltar é android.R.id.home,
conforme você viu no código.
Observação: tenha atenção, pois no Android, temos duas ou mais
classes R. Temos uma classe R que fica no nosso próprio projeto e contém
acesso aos recursos que estão lá na pasta /res/, como imagens e strings com
textos. Porém, veja que nesse caso usamos a classe android.R, isso significa
que esta é a classe R nativa do Android.
Para finalizar este tópico, veja novamente a figura que mostramos as telas
do aplicativo de tarefas com o cadastro funcionando. Na tela da tarefa, vamos
adicionar posteriormente o botão deletar (X) na App Bar. Apenas adiantando, a
ação desse botão também será implementada dentro do método
onOptionsItemSelected(item) que acabamos de fazer. Mas para excluir uma
tarefa, antes precisamos criá-la, e isso vamos fazer no próximo tópico.

4.2 FAB – Floating Action Button (+)

Aquele botão (+) redondo que vamos colocar na lista de tarefas é


conhecido como Floating Action Button (botão flutuante) e também faz parte dos
padrões do Material Design. Como este é um botão flutuante, ele precisa ser
adicionado dentro de um CoordinatorLayout, que é um layout especial do
Material Design que consegue controlar a disposição dos elementos e inclusive
fazer técnicas de scroll (rolagem) mais avançadas que um dia você vai estudar.
Mas por enquanto, vamos criar o seguinte layout.

<?xml version="1.0" encoding="utf-8"?>


<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
app:tint="@android:color/white"
android:src="@android:drawable/ic_input_add"
android:layout_margin="16dp" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Conforme vimos, para adicionar o botão FAB (Floating Action Button),


existe um XML ligeiramente grande que pode assustar no início, mas leia cada
linha com atenção que logo você se acostuma e verá que cada linha faz sentido.
Veja que foi configurado o id android:id="@+id/fab" para o botão, e vale
lembrar também que é comum desenvolvedores Android falaram apenas "botão
FAB" no lugar de Floating Action Button, pois é uma forma mais abreviada,
simples de falar. No Material Design, um botão FAB representa a ação mais
importante da tela e geralmente possui apenas um no layout. Por isso, sempre
utilizarmos o id android:id="@+id/fab". Dito isso, vamos para o código:

package com.example.hellotarefas

...
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

fab.setOnClickListener { onClickAddTarefa() }
}

private fun onClickAddTarefa() {


startActivity(Intent(this, TarefaActivity::class.java))
}
}

Ao executar o projeto novamente no emulador, você verá que


adicionamos o evento de clique no botão FAB e que ele está abrindo a activity
com o formulário da tarefa. Ao abrir o formulário, ele estará vazio e pronto para
salvar uma nova tarefa.
TEMA 5 – SALVANDO AS TAREFAS

Para salvar as tarefas, teremos que adicionar um método salvar(tarefa)


na classe TarefaService, mas antes precisamos ter uma maneira de salvar essa
tarefa. Como explicado anteriormente, vamos salvar as tarefas em uma
HashTable que ficará em memória no aplicativo, ou seja, se você fechar e abrir
o aplicativo, as tarefas serão perdidas. Mas, no momento, isso vai cumprir nosso
objetivo: estudar listas e outros detalhes do Android.
Para prosseguir, altere a classe TarefaService para ficar assim:

import com.example.hellotarefas.Tarefa

object TarefaService {

private var count = 0


// HashTable
private val tarefas = mutableMapOf<Int, Tarefa>()

init {
for (i in 1..3) {
tarefas.put(i, Tarefa(i,"Tarefa $i"))
count = i
}
}
fun save(t: Tarefa) {
if(t.id == 0) {
// Gera um id
t.id = ++count
}
tarefas.put(t.id, t)
}
fun remove(tarefa: Tarefa) {
tarefas.remove(tarefa.id)
}
fun getTarefas(): List<Tarefa> {
return tarefas.values.toList()
}
}

Observe que agora as tarefas ficam salvas em um Map (HashTable), cuja


chave é um Int (id da tarefa) e o conteúdo é o objeto Tarefa.
O método getTarefas() retorna a lista que está contida dentro deste Map.
O método saveTarefa(t) adiciona a tarefa no Map utilizando o id. Caso o
id não exista, significa que é uma nova tarefa, e por isso estamos incrementando
a variável count para simular o id do banco de dados.
O método remove(t) vai remover a tarefa do Map utilizando o seu id.
Com isso, temos métodos para fazer a famosa operação de cadastro,
conhecida como CRUD (Create, Read, Update and Delete). Futuramente,
podemos até alterar a implementação dessa classe para usar banco de dados,
como por exemplo, o SQLite. Essa é a vantagem de separar a lógica em várias
classes e cada uma ter sua responsabilidade.

5.1 Salvando e excluindo uma tarefa

Para finalizar nosso cadastro, vamos mostrar o código final do exemplo.


Apenas para sua conferência, segue o código completo da MainActivity – caso
você tenha seguido todos os passos, já fez esse código.

● MainActivity

package com.example.hellotarefas

import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import br.com.livroandroid.tarefas.adapter.TarefaAdapter
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

fab.setOnClickListener { onClickAddTarefa() }
}
override fun onResume() {
super.onResume()

val tarefas = TarefaService.getTarefas()


recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = TarefaAdapter(tarefas) {
onClickTarefa(it)
}
}
private fun onClickTarefa(t: Tarefa) {
val intent = Intent(this, TarefaActivity::class.java)
intent.putExtra("tarefa",t)
startActivity(intent)
}
private fun onClickAddTarefa() {
startActivity(Intent(this, TarefaActivity::class.java))
}
}

E o código da classe TarefaActivity vai ficar conforme mostramos a seguir


(as partes novas estão destacadas em amarelo).
package com.example.hellotarefas

import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_tarefa.*

class TarefaActivity : AppCompatActivity() {


private var tarefa: Tarefa? = null

override fun onCreate(savedInstanceState: Bundle?) {


super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tarefa)

// Configura botão voltar e o título


supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.title = "Tarefa"

// Recebe o objeto passado como parâmetro


this.tarefa = intent.getSerializableExtra("tarefa") as Tarefa?

// Atualiza a tela com os dados da tarefa


setTarefa(tarefa)

btSalvar.setOnClickListener { onClickSalvar() }
}
private fun setTarefa(tarefa: Tarefa?) {
if(tarefa != null) {
tTitulo.setText(tarefa.titulo)
}
}
// Cria os itens de menu na App Bar (ex: botão deletar)
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_tarefa, menu)
val item = menu?.findItem(R.id.action_delete)
if(item != null) {
item.setVisible(tarefa != null)
}
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
android.R.id.home -> {
finish()
}
R.id.action_delete -> {
onClickDeletar()
}
}
return super.onOptionsItemSelected(item)
}
private fun onClickSalvar() {
val tarefa = getTarefa()
TarefaService.save(tarefa)

finish()
}
private fun onClickDeletar() {
tarefa?.let {
TarefaService.remove(it)
finish()
}
}
private fun getTarefa(): Tarefa {
val n = this.tarefa?: Tarefa()
n.titulo = tTitulo.text.toString()
return n
}
}

Esperamos que a maior parte desse código seja tranquila para você, pois
já fizemos um exemplo parecido de cadastro nas primeiras aulas. Então, vamos
ao que é mais importante.
Primeiramente, o método onCreateOptionsMenu(menu) é responsável
por configurar os botões que vão aparecer na App Bar, e para isso, temos que
criar outro arquivo XML, que vai conter essas ações. Crie uma pasta menu
dentro da pasta res e crie o arquivo menu_tarefa.xml conforme indicado na
figura a seguir:

Figura 9 – Arquivo para criar um menu


● menu_tarefa.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_delete"
app:showAsAction="always"
android:icon="@android:drawable/ic_delete" />
</menu>

O XML de menu contém os itens de menu que vão aparecer na App Bar,
que no nosso caso é apenas o botão de deletar. A figura do botão deixamos com
aquele ícone de (X) padrão do Android e o id foi configurado como
action_delete. Esse id é usado para tratar o evento de quando o usuário tocar
neste item de menu lá no método onOptionsItemSelected(item).

R.id.action_delete -> {
onClickDeletar()
}

Outro ponto importante de código: veja que o objeto Tarefa foi deixado
como atributo da classe, pois precisamos acessar ele dentro dos métodos
onClickSalvar() e onClickDeletar(), assim, esse objeto possui um escopo
global dentro da classe.

private var tarefa: Tarefa? = null

Por isso, lá no método onCreate(bundle), estamos atribuindo o objeto


tarefa para este atributo e não mais uma variável local, observe o 'this.tarefa':

this.tarefa = intent.getSerializableExtra("tarefa") as Tarefa?

Dito isso, existem duas lógicas pequenas dentro dos métodos de salvar e
deletar.
No método onClickSalvar(), é utilizado o operador Elvis explicado na aula
de Kotlin para criar o objeto tarefa. O objetivo é utilizar o objeto tarefa se ele já
existe para atualizar os dados, ou criar uma nova instância do objeto caso ele
não exista. Isso é feito nesta linha:

val n = this.tarefa?: Tarefa()


No caso da atualização da tarefa, isso é importante, pois preservamos a
mesma instância do objeto que foi passado como parâmetro para a activity e,
portanto, é uma tarefa que já possui id.
No método onClickDeletar(), estamos usando a palavra reservada let do
Kotlin que ainda não tínhamos estudado.

private fun onClickDeletar() {


tarefa?.let {
TarefaService.remove(it)
finish()
}
}

O let é utilizado porque o objeto Tarefa? foi declarado com a interrogação


ao criar o atributo de classe e, portanto, pode ser nulo. A expressão entre chaves
do let só vai executar caso esse objeto não seja nulo.
Esse código faz o mesmo que este 'if', e podemos dizer que é uma sintaxe
mais moderna e adotada pelos desenvolvedores Kotlin. O let também está
presente em outras linguagens como o Swift utilizado no iOS, aliás, muito do que
estudamos aqui funciona de forma similar no iOS. Um dia, você vai perceber que
as linguagens são todas iguais e o que importa são os conceitos, técnicas de
programação e design patterns que você conhece, ou seja, no final, código é só
código.

private fun onClickDeletar() {


val t = this.tarefa
if(t != null) {
TarefaService.remove(t)
finish()
}
}

FINALIZANDO

Nesta aula, aprendemos a criar listas com os componentes RecyclerView


e CardView, e fizermos um exercício de como criar um formulário de cadastro.
REFERÊNCIAS

LECHETA, R. Android Essencial com Kotlin. 2. ed. 2018.

Você também pode gostar