Baixe o app para aproveitar ainda mais
Prévia do material em texto
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 deveser 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. Porpadrã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.
Compartilhar