Prévia do material em texto
PROGRAMAÇÃO IV
AULA 4
Prof. Ricardo Rodrigues Lecheta
CONVERSA INICIAL
Olá, seja bem-vindo(a) a esta aula.
Anteriormente, estudamos como criar layouts em XML, e, nesta aula,
vamos “colocar a mão na massa” no código da activity.
CONTEXTUALIZANDO
A classe Activity representa uma tela do aplicativo e é responsável por
controlar os eventos e a lógica desta tela. Vamos estudar em mais detalhes o
que é uma activity, revisar como fazer o tratamento de eventos e como fazer a
navegação entre telas.
TEMA 1 – ACTIVITY
A classe Activity representa uma tela do aplicativo e é responsável por
controlar os eventos e a lógica dessa tela. Para criar uma activity, devemos ter
uma classe filha de Activity ou AppCompatActivity. A diferença entre as duas
é que a classe Activity é embarcada no sistema operacional e vai ter uma versão
diferente do código dela em um Android 5.0 e um Android 10. Para evitar esses
problemas de compatibilidade com diferentes versões do Android, o Google criou
um pacote que é chamado de biblioteca de compatibilidade e recomenda que as
classes desse pacote sejam utilizadas no lugar das nativas. Por isso, ao criarmos
o projeto, o Android Studio fez a nossa MainActivity ser filha de
AppCompatActivity.
class MainActivity : AppCompatActivity() {
Lembra do arquivo styles.xml? Nele, também usamos o tema de
compatibilidade, que traz o Material Design e a mesma interface para todas as
versões do Android.
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
A vantagem de utilizar a biblioteca de compatibilidade é que ela é
adicionada no arquivo app/build.gradle e pode ser atualizada sempre que o
Google lançar versões mais novas. Caso você utilize a classe Activity padrão,
o código dela só é atualizado quando atualizar todo o sistema operacional do
seu celular, entende?
● app/build.gradle
implementation 'androidx.appcompat:appcompat:1.2.0'
Bom, o tema é um pouco complicado, mas na prática você não precisa se
preocupar muito com isso, basta sempre que criar uma activity, herdar da classe
AppCompatActivity, conforme fizemos até agora. Nós também estudamos que
uma activity possui o método onCreate(bundle), que é chamado ao inicializar
essa tela. E que o método setContentView(layout) é usado para configurar o XML
que será usado como layout desta tela. Portanto, um template básico de uma
activity é sempre assim:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Feita essa revisão básica, vamos estudar em mais detalhes o tratamento
de eventos dentro de uma activity.
1.1 Tratamento de eventos e o método setOnClickListener
Anteriormente, fizemos o seguinte código para tratar os eventos dos
botões:
package com.example.helloandroid
. . .
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.btLogin).setOnClickListener {
startActivity(Intent(this,HomeActivity::class.java))
}
findViewById<TextView>(R.id.btEsqueciSenha).setOnClickListener {
startActivity(Intent(this,EsqueciSenhaActivity::class.java))
}
findViewById<TextView>(R.id.btCadastrar).setOnClickListener {
startActivity(Intent(this,CadastroActivity::class.java))
}
}
}
O método setOnClickListener recebe como parâmetro um objeto que
implementa a interface OnClickListener. O único método desta interface é o
onClick(View).
public interface OnClickListener {
void onClick(View view);
}
Talvez você já deve ter deduzido que usamos lambdas para implementar
a interface OnClickListener de uma forma simples. Mas como existem várias
maneiras de implementar uma interface, vamos estudá-los agora.
1.2 Tratando eventos com o “this”
Para implementar uma interface, usamos uma vírgula logo seguida da
definição da classe mãe e colocamos o nome da interface que queremos
implementar, nesse caso a View.OnClickListener. Se tivesse mais de uma
interface, podemos adicionar várias separadas por vírgula. Quando uma classe
implementa uma interface, ela é obrigada a implementar todos os métodos da
classe, que neste caso é apenas o onClick(view: View?).
. . .
class MainActivity : AppCompatActivity(), View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.btLogin).setOnClickListener(this)
findViewById<TextView>(R.id.btEsqueciSenha).setOnClickListener(this)
findViewById<TextView>(R.id.btCadastrar).setOnClickListener(this)
}
override fun onClick(view: View?) {
when(view?.id) {
R.id.btLogin -> {
startActivity(Intent(this,HomeActivity::class.java))
}
R.id.btEsqueciSenha -> {
startActivity(Intent(this,EsqueciSenhaActivity::class.java))
}
R.id.btCadastrar -> {
startActivity(Intent(this,CadastroActivity::class.java))
}
}
}
}
A vantagem de fazer desta forma é que o método setOnClickListener
pode ser configurado apenas passando a referência da própria classe com a
palavra reservada 'this', pois sabemos que a classe é compatível com a interface
desejada.
findViewById<Button>(R.id.btLogin).setOnClickListener(this)
A desvantagem dessa implementação é que todos os botões vão chamar
o método onClick(view), e lá dentro temos que fazer um if ou switch para
verificar qual botão foi clicado. No Kotlin, não existe o controle de fluxo com o
switch, e no lugar temos o when, mas como podemos ver no código, é bem
simples de se utilizar. Para maiores detalhes, leia a documentação oficial no link
a seguir: <https://kotlinlang.org/docs/reference/control-flow.html#when-
expression>.
1.3 Tratando eventos com classes anônimas
Outra maneira de implementar uma interface é criar uma classe anônima,
que é um trecho de código que pode ser passado como argumento para uma
função e automaticamente implementar a interface. No Kotlin, para criar uma
interface anônima, é utilizada a palavra reservada 'object:', seguida da
declaração da interface e de todos os métodos que ela possui. Na prática, um
objeto é criado nessa parte do código e possui todos os métodos da interface
desejada.
findViewById<Button>(R.id.btLogin).setOnClickListener(object:
View.OnClickListener{
override fun onClick(view: View?) {
startActivity(Intent(this@MainActivity,HomeActivity::class.java))
}
})
Principalmente para quem está iniciando, essa sintaxe é uma das mais
complicadas. Mas sempre que a interface possui apenas um método, que é o
caso da OnClickListener, podemos simplificá-la usando as lambdas.
1.4 Tratando eventos com Lambdas
Conforme estudamos sobre Kotlin, a sintaxe de abre e fecha chaves { } é
uma lambda, ou seja, é uma maneira mais simples de passar um código como
parâmetro para funções e inclusive métodos que recebem uma interface. Neste
caso, como a interface OnClickListener possui apenas um método, podemos
implementá-lo diretamente com lambdas, assim:
findViewById<Button>(R.id.btLogin).setOnClickListener {
startActivity(Intent(this,HomeActivity::class.java))
}
Internamente, foi criado uma classe anônima que implementa a interface
OnClickListener e seu método onClick(View). Simples, não é?
1.5 Organizando em métodos
Com certeza, a sintaxe das lambdas atualmente é a mais utilizada pelos
desenvolvedores devido à sua simplicidade.
Para concluir, algo que gosto de fazer para deixar o código ainda mais
organizado é criar ummétodo separado para cada evento, como feito no próximo
exemplo. Note que foi criado o método onClickLogin() específico para o evento
de login. O mesmo foi feito para os outros botões. Isso torna o código muito
simples de entender. A vantagem é que, dentro da lambda, sempre teremos
apenas uma linha de código, que é a chamada do método que vai tratar o evento.
package com.example.helloandroid
. . .
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.btLogin).setOnClickListener {
// Delega o tratamento para o método correto
onClickLogin()
}
findViewById<TextView>(R.id.btEsqueciSenha).setOnClickListener {
onClickEsqueciSenha()
}
findViewById<TextView>(R.id.btCadastrar).setOnClickListener {
onClickCadastrar()
}
}
// Um método para cada evento aqui
private fun onClickLogin() {
startActivity(Intent(this,HomeActivity::class.java))
}
private fun onClickEsqueciSenha() {
startActivity(Intent(this,EsqueciSenhaActivity::class.java))
}
private fun onClickCadastrar() {
startActivity(Intent(this,CadastroActivity::class.java))
}
}
Essa organização dos métodos é muito importante para dar manutenção
no projeto. Imagine que precisamos mexer no código que trata o evento de um
botão. Organizando o código dessa maneira, é possível achar rapidamente um
método apenas utilizando a tecla de atalho Ctrl+F12 (Windows) ou
Cmd+Fn+F12 (Mac). Esse atalho vai abrir a janela do assistente e depois basta
digitar 'onClick' para filtrar os métodos que você deseja procurar. É possível usar
as setas do teclado para navegar, pressionar <enter> e pronto. Conseguimos
chegar no ponto do código que precisamos sem nem tocar no mouse.
Figura 1 – Atalho
1.6 Tratando eventos pelo XML
Conforme explicado, a solução mais adotada no mercado é usar lambdas,
mas ainda falta explicar uma maneira de adicionar eventos, que é utilizar a tag
onClick diretamente no XML, configurando o nome do método que vai tratar o
evento. Essa configuração é simples assim:
<Button
android:id="@+id/btLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Login"
android:onClick="onClickLogin"/>
Feito isso, basta adicionar o método desejado na classe da activity.
fun onClickLogin(view: View) {
// código aqui
}
Esse jeito de adicionar eventos pode parecer a melhor solução no início,
mas na prática é pouco usado. O motivo é porque ao olhar o código da classe,
não conseguimos detectar de maneira rápida de onde esse método está sendo
chamado. Para descobrir, temos que olhar o XML. É claro que nesse exemplo
simples é óbvio, mas em códigos maiores isso pode ser um problema.
Portanto, a solução que vamos adotar é o uso de lambdas.
TEMA 2 – TRATANDO EVENTO DE LOGIN
Já fizemos o layout do formulário de login e aprendemos a tratar os
eventos. Desta vez, vamos aprender a ler os textos que foram digitados pelo
usuário. Para isso, precisamos criar um identificador, chamado apenas de 'id',
para cada view que queremos ler o texto ou seu valor. Nós já estudamos sobre
esse “id” quando adicionamos os eventos nos botões, mas sempre é bom revisar
os conceitos.
Abra o arquivo activity_main.xml e encontre os dois campos de texto
(EditText) do login e da senha. Feito isso, adicione os ids em cada um deles. Eu
costumo colocar a letra 't' antes do id sempre que é um EditText:
<EditText
android:id="@+id/tLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
. . .
<EditText
android:id="@+id/tSenha"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
/>
Para adicionar o id em uma view no XML, é utilizada a tag android:id. O
id é sempre prefixado por '@+id/'. A sintaxe é estranha, mas logo você se
acostuma :-). Com isso, já podemos encontrar essas views no código com o
método findViewById(id). A implementação do método onClickLogin(), lendo
os textos digitados, fica assim:
private fun onClickLogin() {
// Encontra as views
val tLogin = findViewById<TextView>(R.id.tLogin)
val tSenha = findViewById<TextView>(R.id.tSenha)
// Lê os textos
val login = tLogin.text.toString()
val senha = tSenha.text.toString()
if(login == "ricardo" && senha == "123") {
// Login OK, vai para a Home
startActivity(Intent(this,HomeActivity::class.java))
} else {
// Erro de Login
}
}
Faça essa alteração de código e execute o projeto novamente no
emulador. Desta vez, o login só será bem-sucedido se digitarmos corretamente
os dados.
2.1 Criando alertas
Até o momento, tratamos no código apenas o login feito com sucesso.
Neste próximo exemplo, vamos mostrar um alerta caso o login esteja incorreto.
Primeiramente, faça import da classe AlertDialog:
import androidx.appcompat.app.AlertDialog
E deixe o método onClickLogin() assim:
private fun onClickLogin() {
. . .
if(login == "ricardo" && senha == "123") {
// OK
startActivity(Intent(this,HomeActivity::class.java))
} else {
// Erro
val dialog = AlertDialog.Builder(this).create()
dialog.setTitle("Android")
dialog.setMessage("Login incorreto, digite os dados
novamente")
dialog.setButton(
AlertDialog.BUTTON_NEUTRAL, "OK"
) { _, which ->
dialog.dismiss()
}
dialog.show()
}
}
Feito isso, um alerta será mostrado quando tivermos um erro no login:
Figura 2 – Alerta
Créditos: khuruzero/Shutterstock.
Você deve ter reparado que o código para mostrar o alerta é um tanto
quanto grande, portanto, vamos criar uma extensão para facilitar a nossa vida.
Conforme estudamos sobre Kotlin, uma extensão permite adicionar métodos em
uma classe sem a necessidade de criar uma classe filha, portanto, nosso objetivo
será adicionar um método chamado alert() na classe da Activity.
Como estamos utilizando a biblioteca de compatibilidade do Google,
vamos criar uma extensão para a classe AppCompatActivity.
Crie um pacote “extensions” para armazenar as extensões do projeto e
crie o arquivo Activity-Extensions.kt, conforme indicado na figura a seguir.
Figura 3 – Arquivo Activity Extensions
É um padrão de nomenclatura utilizarmos o nome da classe que
queremos customizar no nome do arquivo, desta forma, quem bater o olho nesse
arquivo, vai saber que se trata de uma extensão que vai adicionar métodos na
classe Activity.
● Activity-Extensions.kt
package com.example.helloandroid.extensions
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
fun AppCompatActivity.alert(msg: String) {
val dialog = AlertDialog.Builder(this).create()
dialog.setTitle("Android")
dialog.setMessage(msg)
dialog.setButton(
AlertDialog.BUTTON_NEUTRAL, "OK"
) { _, which ->
dialog.dismiss()
}
dialog.show()
}
Pronto. Falando um português bem claro, essa extensão está dizendo que
agora todas as activities (filhas de AppCompatActivity) possuem o método
alert(msg).
Para utilizar a extensão no código da classe MainActivity, precisamos
fazer o import do método “alert”. Veja que o pacote dele foi definido no arquivo
da extensão.
import com.example.helloandroid.extensions.alert
Por fim, o código da activity fica simples assim:
private fun onClickLogin() {
. . .
if(login == "ricardo" && senha == "123") {
// OK
startActivity(Intent(this,HomeActivity::class.java))
} else {
// Erro
alert("Login incorreto, digite os dados novamente")
}
}Neste exemplo, vimos o quanto uma extensão deixa o código mais
simples, facilitando a manutenção do aplicativo.
TEMA 3 – LOGCAT E DEBUG
Provavelmente, você está acostumado a utilizar aquelas funções print e
println disponível em várias linguagens como Java e Kotlin, mas no Android, a
maneira correta de imprimir mensagens no console é por meio da ferramenta
LogCat. A vantagem do LogCat é que conseguimos categorizar os logs utilizando
tags e também podemos logar mensagens por níveis de severidade, como info,
debug, erro, etc. Para começar a brincar com o LogCat, faça o import da classe
Log:
import android.util.Log
Para praticarmos, imagine que queremos debugar a função de login, e
imprimir os textos que foram digitados pelo usuário do login e senha. Para isso,
basta utilizar esta linha de código:
Log.d("empresa","Login: $login, senha: $senha")
A seguir, podemos ver o código atualizado da função de login.
private fun onClickLogin() {
// Encontra as views
val tLogin = findViewById<TextView>(R.id.tLogin)
val tSenha = findViewById<TextView>(R.id.tSenha)
// Lê os textos
val login = tLogin.text.toString()
val senha = tSenha.text.toString()
Log.d("empresa","Login: $login, senha: $senha")
if(login == "ricardo" && senha == "123") {
// OK
startActivity(Intent(this,HomeActivity::class.java))
} else {
// Erro
alert("Login incorreto, digite os dados novamente")
}
}
Para visualizar os logs, na parte inferior do Android Studio, encontre a
janela 6: Logcat, ou pressione Alt+6 (Windows) ou Cmd+6 (Mac). Por padrão, o
LogCat mostra todos os logs do sistema operacional do Android e o que
precisamos é encontrar a mensagem que foi escrita com a tag empresa. Na
janela do LogCat, clique no combo que filtra os logs (lá na direita), e selecione a
opção Edit Filter Configuration. Preencha essa janela conforme indicado na
figura e clique em OK para salvar.
Figura 4 – Create new logcat filter
No combo que filtra os logs (lá na direita), selecione a tag empresa,
conforme indicado na figura. Assim, podemos ver apenas os logs que estamos
interessados. Entendeu para que serve a tag nos logs? A tag basicamente é um
filtro.
Figura 5 – Tag
Também podemos logar mensagens no LogCat com outros níveis de
severidade, exemplo, Log.i (info), Log.w (warning), Log.d (debug), Log.v
(verbose), Log.e (erro). No centro da janela do LogCat, você verá um combo que
está escrito Verbose, e lá você pode filtrar apenas o nível de severidade que
estamos interessados.
Com o tempo, você vai se acostumar com esses logs e, com certeza, eles
vão ser um grande aliado ao debugar o código dos aplicativos.
3.1 Visualizando erros no LogCat
Algo muito importante no desenvolvimento de qualquer código é aprender
a ler as mensagens de erro e as famosas stack traces (pilha com os erros de
uma exceção). No Android, sempre que o aplicativo travar e encerrar, significa
que uma exceção não tratada foi lançada e podemos visualizar todos os detalhes
desses logs usando o LogCat.
Para brincarmos com isso, vamos simular um erro :-). Primeiramente,
vamos comentar a configuração da HomeActivity do arquivo de manifesto, pois
vamos adicionar um bug proposital apenas para aprendermos a visualizar os
logs. Um comentário em XML começa com '<!--' e termina com '-->'. Você
pode selecionar o trecho do XML que deseja comentar e utilizar a tecla de atalho
Ctrl + Shift + / (WIndows) ou Cmd + Shift + / (Mac).
<!-- <activity
android:name=".HomeActivity"
android:parentActivityName=".MainActivity"
android:label="Home"/> -->
Depois de comentar a configuração da HomeActivity, execute
novamente o projeto no emulador. Ao fazer o login, o aplicativo vai travar. Ok,
isso já era esperado, mas como fazemos para descobrir o erro?
É simples, basta abrir o LogCat e selecionar o nível de severidade Error.
Outro detalhe importante é desmarcar todas as tags no combo que filtra as tags
(lá na direita), pois agora queremos visualizar todos os erros. Deixe no combo a
opção No Filters selecionada.
Figura 6 – Error
A figura acima mostra que foi lançada uma exceção, e inclusive a
mensagem é bastante clara: "ActivityNotFoundException: Unable to find explicit
activity class {HomeActivity}; have you declared this activity in your
AndroidManifest.xml?".
O mais interessante é que o Android nos ajuda, e depois de não encontrar
a configuração da HomeActivity, ainda nos pergunta se fizemos a configuração
dessa activity no arquivo de manifesto :-).
Uma vez que já brincamos de visualizar o erro, volte à configuração da
activity (desfaça aquele comentário) e vamos continuar os nossos estudos. Mas
lembre-se, sempre que o aplicativo travar, a primeira tarefa a fazer é verificar os
erros no LogCat.
3.2 Debugando o código
Para ativar um breakpoint no código, clique na parte esquerda do editor,
na área cinza onde ficam indicadas as linhas do código. Na figura a seguir, o
breakpoint é representado pela bolinha vermelha na linha 37.
Figura 7 – Breakpoint do código
Créditos: khuruzero/Shutterstock.
Com o breakpoint devidamente adicionado, execute o código com o botão
Debug em vez do Run que automaticamente o breakpoint será acionado quando
esse trecho de código for chamado.
Observe na figura que no canto inferior esquerdo fica a pilha com as
chamadas do código, e no lado direito, podemos ver o valor das variáveis e
depurar o código passo a passo. Enfim, debug de código é um assunto básico e
provavelmente você já deve ter feito em outra ferramenta.
TEMA 4 - ORGANIZANDO O CÓDIGO DO PROJETO
Já aprendemos a utilizar os logs e o debug no Android Studio, e agora
vamos estudar um pouco sobre organização de código, o que vai ser bom para
praticarmos um pouco mais sobre como criar classes e a sintaxe do Kotlin.
4.1 Login
Até o momento, a lógica de login está fixa apenas para ilustrar a ideia:
if(login == "ricardo" && senha == "123") {
Mas na prática, o aplicativo provavelmente vai fazer uma consulta na
internet para validar este login. Essa consulta geralmente é feita com um web
service, que é chamado popularmente de API. Nós ainda vamos estudar como
utilizar web services, mas, por enquanto, o importante é já deixar a estrutura de
código preparada.
Nosso objetivo agora é criar as classes Usuario e LoginService para
encapsular a lógica de login. Isso é importante, pois a medida que o código do
aplicativo ficar mais complexo, não é recomendado deixar toda a lógica na classe
da activity, pois isso iria poluir muito o código e deixar ele difícil de dar
manutenção. Na prática, a activity deve atuar como um Controller do padrão
MVC, ou seja, ela deve ser um intermediador entre a view/layout e a lógica de
negócios. Ou seja, a activity recebe os eventos da tela e delega o trabalho para
classes especializadas.
Dito isso, vamos criar a classe Usuario que vai armazenar os dados do
usuário do nosso aplicativo. Também vamos criar a classe LoginService, que
vai conter a lógica para fazer o login. Resumindo, podemos dizer que teremos
este fluxo para fazer um login:
> Activity > Service > API (web service)
Muito bem, vamos para o código que vai ficar mais fácil de entender. Crie
um pacote “domain” conforme demonstrado na próxima figura e crie as classes
LoginService e Usuario.
Figura 8 – Domain
A seguir, podemos ver o código da classe Usuário. Ela possui apenas
dois atributos, o nome e email.
package com.example.helloandroid.domain
data class Usuario(
val nome: String,
val email: String
)
A lógica do login vamos deixar na classe LoginService. Por enquanto,
continuamos com os dados fixos, mas o importante é que já estamos
organizando o código e futuramente só vamos alterar esta classe.
package com.example.helloandroid.domain
class LoginService {
fun login(login:String,senha: String): Usuario? {
if(login == "ricardo" && senha == "123") {
return Usuario("Ricardo","a@a.com")
} else if(login == "teste" && senha == "123") {
return Usuario("Teste","b@b.com")
}
return null
}
}
Observe que o método de login retorna um Usuario? com a interrogação,
e conforme estudamos sobre Kotlin, isso indica que o retorno pode ser nulo. Para
usar esta classe, basta criar uma nova instância de um objeto e chamar o método
login(login,senha) da classe LoginService, conforme indicado a seguir:
private fun onClickLogin() {
// Encontra as views
val tLogin = findViewById<TextView>(R.id.tLogin)
val tSenha = findViewById<TextView>(R.id.tSenha)
// Lê os textos digitados
val login = tLogin.text.toString()
val senha = tSenha.text.toString()
// Valida o login
val service = LoginService()
val user = service.login(login,senha)
if(user != null) {
// OK
startActivity(Intent(this,HomeActivity::class.java))
} else {
// Erro
alert("Login incorreto, digite os dados novamente")
}
}
Pronto! Acabamos de organizar nosso código e toda a lógica de login ficou
na classe LoginService. E para validar se o login foi bem-sucedido, basta
verificar se o usuário que retornou do login não é nulo.
Quebrar o código em pequenas classes é um dos conceitos mais
importantes da Orientação a Objetos, pois é melhor cada classe ser
especializada em um assunto específico e fazer poucas coisas, do que ter uma
classe que faz tudo, como era antes com a MainActivity.
Como estudo complementar, recomendo que você leia sobre o princípio
de Baixo Acoplamento e Alta Coesão, que é um dos pilares da Orientação a
Objetos.
4.2 O método finish
Quando fizemos o login, navegamos para a tela da HomeActivity, e ao
clicar no botão de voltar, o aplicativo volta para a tela de login. Esse é o
comportamento padrão do Android, pois ele vai empilhando as activities sempre
que o aplicativo vai abrindo as telas. Internamente, essa pilha é chamada de
activity stack. E se fosse um requisito encerrar a tela de login depois do login
com sucesso? Neste caso, podemos chamar o método finish(), conforme
demonstrado a seguir.
val service = LoginService()
val user = service.login(login,senha)
if(user != null) {
// OK
startActivity(Intent(this,HomeActivity::class.java))
finish()
}
No arquivo de manifesto, encontre a HomeActivity e remova a tag
parentActivityName. Desta maneira, o botão de voltar na tela da Home não vai
aparecer:
<activity
android:name=".HomeActivity"
android:parentActivityName=".MainActivity"
android:label="Home"/>
Faça esta alteração e execute o aplicativo novamente. Desta vez, depois
de fazer o login e abrir a tela da Home, não vai existir nenhum botão de voltar na
AppBar (lá em cima na esquerda). Ao clicar no botão voltar nativo do Android, o
aplicativo será fechado, pois não existe nenhuma activity atrás desta.
O que fizemos foi:
1. Chamamos o método finish() logo depois do login. Isso encerrou a tela
da LoginActivity;
2. Removemos o botão de voltar da AppBar.
4.3 Esqueci a Senha
Vamos continuar praticando o desenvolvimento do nosso aplicativo e
vamos simular a funcionalidade do esqueci a senha. Vou ser bem prático desta
vez, porque acredito que é uma funcionalidade bem simples, mas é muito
importante para revisarmos e consolidar os conceitos que aprendemos.
A lógica da recuperação de senha ficará na classe
EsqueciSenhaService, portanto, crie essa classe da mesma maneira que
fizemos com a LoginService. Ela também poderá ficar no pacote “domain”.
O método recuperarSenha(login) recebe o login do usuário e vai
devolver um booleano (true/false) para informar se a senha foi recuperada com
sucesso. Claro que estamos apenas simulando a lógica, mas já estamos
estruturando o código, ok?
package com.example.helloandroid.domain
import android.util.Log
class EsqueciSenhaService {
fun recuperarSenha(login:String): Boolean {
Log.d("empresa", "Simulando o recuperar a senha:
$login")
return true
}
}
Feito isso, abra o arquivo activity_esqueci_senha.xml e adicione um id
para o campo de texto (EditText) e no botão de enviar (Button):
<?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"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Digite seu login" />
<EditText
android:id="@+id/tLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btEnviar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Enviar" />
</LinearLayout>
Com isso, podemos adicionar um evento neste botão, e chamar a lógica
da classe de negócio (que chamamos apenas de service). Observe que foi
utilizado o mesmo id '@+id/tLogin' que já tínhamos usado no layout de login. Isso
não tem problema e inclusive é recomendado para evitar criar muitos ids.
package com.example.helloandroid
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import com.example.helloandroid.domain.EsqueciSenhaService
import com.example.helloandroid.extensions.alert
class EsqueciSenhaActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_esqueci_senha)
findViewById<Button>(R.id.btEnviar).setOnClickListener {
onClickEnviar()
}
}
private fun onClickEnviar() {
val tLogin = findViewById<TextView>(R.id.tLogin)
val login = tLogin.text.toString()
val service = EsqueciSenhaService()
val ok = service.recuperarSenha(login)
if(ok) {
alert("Sua nova senha foi enviada para o seu-email.")
} else {
alert("Ocorreu um erro ao recuperar a senha.")
}
}
}
Depois de chamar o método de recuperar a senha, veja que estamos
armazenando o resultado em uma variável booleana. Vale lembrar que o tipo da
variável que destaquei abaixo em amarelo geralmente é omitido, portanto
acostume-se com isso.
val ok:Boolean = service.recuperarSenha(login)
Ao executar esse exemplo e clicar no botão, você verá o alerta de
sucesso, pois até o momento a classe de negócio sempre está retornando true
(ok).
Figura 9 – Recuperação de senha
4.4 Executar ação ao clicar no OK do alerta
Uma funcionalidade muito comum em aplicativos é a necessidade de
executar alguma função logo depois de clicar no botão Ok de um alerta. Para dar
um exemplo real, podemos dizer que se a recuperação de senha for bem-
sucedida, ao clicar no Ok, podemos fechar esta tela automaticamente. Mas como
podemos fazer essa lógica?
O que podemos fazer é passar como parâmetro uma função que
popularmente chamamos de callback, que traduzindo significa “retorno”. Essa
função não recebe parâmetros e não possui retorno, portanto é Unit (void). No
código, veja que esta função é chamada ao criar o código do AlertDialog.
package com.example.helloandroid.extensions
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
fun AppCompatActivity.alert(msg: String, callback: () -> Unit = {}) {
val dialog = AlertDialog.Builder(this).create()
dialog.setTitle("Android")
dialog.setMessage(msg)
dialog.setButton(
AlertDialog.BUTTON_NEUTRAL, "OK"
) { _, which ->
dialog.dismiss()callback() // Aqui chamamos a função callback
}
dialog.show()
}
Pronto, agora podemos passar essa função como parâmetro quando
mostrarmos o alerta de sucesso. Como essa função de callback é o último
parâmetro do método alert(), podemos usar a sintaxe das chaves e passar uma
lambda como argumento.
private fun onClickEnviar() {
val tLogin = findViewById<TextView>(R.id.tLogin)
val login = tLogin.text.toString()
val service = EsqueciSenhaService()
val ok:Boolean = service.recuperarSenha(login)
if(ok) {
alert("Sua nova senha foi enviada para o seu-email.") {
finish()
}
} else {
alert(
"Ocorreu um erro ao recuperar a senha.")
}
}
Pronto! Agora é só testar a tela de esqueci a senha novamente. Desta
vez, depois de clicar no botão de Ok do alerta, a tela será fechada
automaticamente e o aplicativo vai voltar para a tela de login, pois chamamos o
método finish().
4.5 Kotlin Extensions
O Kotlin Extensions é um plugin do gradle que cria automaticamente
variáveis no código com os mesmos ids que foram definidos no XML.
Atualmente, ao criar um projeto no Android Studio, o plugin já é configurado no
arquivo app/build.gradle, mas se precisar fazer manualmente, é só adicionar
esta linha.
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
Dependendo da versão do Android Studio, a configuração pode ser assim.
Caso a linha do Kotlin Extensions esteja faltando, favor adicionar.
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
}
O Kotlin Extensions cria automaticamente as variáveis pelo id que estão
definidos no layout, e depois que você aprender a usá-lo, vai querer me matar
por não ter explicado isso antes :-) Mas é que era necessário explicar a base
para depois evoluir para os atalhos. Dando um exemplo prático, esta linha:
findViewById<Button>(R.id.btEnviar).setOnClickListener {
Pode ser substituída por:
btEnviar.setOnClickListener {
E esta linha pode ser removida, pois a variável tLogin é criada
automaticamente pelo Kotlin Extensions :-)
val tLogin = findViewById<TextView>(R.id.tLogin)
Entendeu, né? O id que você definir no XML vira uma variável aqui no
código. Mas para essa mágica funcionar, é necessário fazer um import na classe,
referenciando o layout XML de onde vamos buscar os ids:
import kotlinx.android.synthetic.main.activity_esqueci_senha.*
O código final fica assim. Está destacado em amarelo as alterações.
package com.example.helloandroid
...
import kotlinx.android.synthetic.main.activity_esqueci_senha.*
class EsqueciSenhaActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_esqueci_senha)
btEnviar.setOnClickListener {
onClickEnviar()
}
}
private fun onClickEnviar() {
val login = tLogin.text.toString()
val service = EsqueciSenhaService()
val ok:Boolean = service.recuperarSenha(login)
if(ok) {
alert("Sua nova senha foi enviada para o seu-email.") {
finish()
}
} else {
alert(
"Ocorreu um erro ao recuperar a senha.")
}
}
}
TEMA 5 – MELHORANDO O FORMULÁRIO DE CADASTRO
Para finalizar esta aula, vamos concluir o formulário de cadastro e
aprender a utilizar o ScrollView, e também a ler os valores digitados no
RadioGroup e Checkbox.
5.1 ScrollView
No próximo exemplo, vamos adicionar o ScrollView no layout XML do
formulário de cadastro. Essa view é utilizada para configurar a rolagem
automática da tela, caso não exista espaço na vertical para mostrar todos os
elementos.
A estrutura básica do ScrollView podemos ver no código abaixo. Um
ScrollView é um gerenciador de layout que pode ter apenas uma tag filha, e
geralmente é um LinearLayout (vertical), o qual possui todas as views uma
embaixo da outra. A combinação dessas duas tags cria um formulário com
rolagem automática, simples assim.
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- View 1 -->
<!-- View 2 -->
<!-- . . . -->
<!-- . . . -->
<!-- . . . -->
<!-- View 100 -->
</LinearLayout>
</ScrollView>
5.2 Cadastro e outras views de formulário
Vamos praticar mais um pouco e concluir o formulário de cadastro. O
objetivo será criar uma classe CadastroModel que conterá todos os dados
digitados no formulário (nome, login, email, sexo) e chamar uma classe
CadastroService que vai simular o cadastro.
Existem três diferenças listadas a seguir para o layout mostrado
anteriormente, mas é pouca coisa e a ideia é você praticar.
1. Foi adicionado um campo de login no formulário. A ideia deste cadastro
fictício é que você digite seu login e alguns dados cadastrais, e uma senha
aleatória seja enviada para seu email.
2. Foram adicionados ids para todas as views que queremos interagir, como
por exemplo: os campos de texto, o checkbox de aceitar os termos de uso,
o radio button do sexo e o botão de cadastrar.
3. Foi adicionado um ScrollView como a tag raiz do layout. O LinearLayout
vertical que contém todos os campos do formulário ficou dentro do
ScrollView. Essa view é mágica e fará a rolagem (scroll) automaticamente
caso a tela seja pequena e não consiga mostrar todos os campos do
formulário.
A seguir, podemos ver o código completo do arquivo
activity_cadastro.xml, em amarelo estão destacadas as alterações feitas no
arquivo. Faça tudo com calma e atenção e depois prossiga com a aula.
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nome" />
<EditText
android:id="@+id/tNome"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Login" />
<EditText
android:id="@+id/tLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Email" />
<EditText
android:id="@+id/tEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sexo" />
<RadioGroup
android:id="@+id/radioSexo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton
android:id="@+id/radioMasculino"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Masculino" />
<RadioButtonandroid:id="@+id/radioFeminino"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Feminino" />
</RadioGroup>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginVertical="16dp"
android:background="#eeeeee" />
<CheckBox
android:id="@+id/checkTermos"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Aceito os termos de uso" />
<Button
android:id="@+id/btCadastrar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Cadastrar" />
</LinearLayout>
</ScrollView>
Adicionar um evento no botão de cadastrar e ler os campos de texto do
EditText nós já sabemos como fazer. Neste próximo exemplo, vamos aprender
como ler os valores das views CheckBox e RadioGroup.
Primeiramente, vamos criar uma classe de modelo que vai conter todos
os dados necessários para cadastrar o usuário:
package com.example.helloandroid.domain
data class CadastroModel(
var nome: String = "",
var login: String = "",
var email: String = "",
var sexo: String = ""
)
A lógica de cadastrar ficará na classe CadastroService. Por enquanto,
ela não faz nada, mas na prática, ela poderia chamar uma API do servidor para
efetivar o cadastro. O importante é você entender como ir fazendo essa
organização de código.
package com.example.helloandroid.domain
import android.util.Log
class CadastroService {
fun cadastrar(model: CadastroModel): Boolean {
Log.d("empresa","Cadastro $model")
return true
}
}
O método cadastrar(model) da classe CadastroService recebe um
objeto que chamamos de modelo (CadastroModel). Portanto, o que temos que
fazer na activity é preencher esse objeto ao clicar no botão de cadastrar.
O código completo da activity podemos ver a seguir:
package com.example.helloandroid
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.helloandroid.domain.CadastroModel
import com.example.helloandroid.domain.CadastroService
import com.example.helloandroid.extensions.alert
import kotlinx.android.synthetic.main.activity_cadastro.*
class CadastroActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_cadastro)
btCadastrar.setOnClickListener {
onClickCadastrar()
}
}
private fun onClickCadastrar() {
val termosOk = checkTermos.isChecked
if (!termosOk) {
alert("Aceite os termos para continuar")
} else {
// Cria objeto de cadastro
val model = getCadastroModel()
val service = CadastroService()
val ok: Boolean = service.cadastrar(model)
if (ok) {
alert("Cadastro realizado com sucesso.\nSua
senha foi enviada para o email.") {
finish()
}
} else {
alert("Ocorreu um erro ao cadastrar.")
}
}
}
// Cria o objeto de modelo copiando os dados do form
private fun getCadastroModel(): CadastroModel {
val model = CadastroModel()
model.nome = tNome.text.toString()
model.login = tLogin.text.toString()
model.email = tEmail.text.toString()
model.sexo = if (radioSexo.getCheckedRadioButtonId() ==
R.id.radioMasculino) "M" else "F"
return model
}
}
Veja a seguir algumas explicações sobre o código:
Para descobrir se o CheckBox está selecionado, basta chamar o método
isChecked() que retorna um boolean.
val termosOk = checkTermos.isChecked
Na brincadeira que estamos fazendo, caso o checkbox não seja
selecionado, o aplicativo vai mostrar um alerta conforme mostrado na figura:
Figura 10 – Cadastro
Créditos: khuruzero/Shutterstock.
Depois de aceitar os termos, precisamos copiar os dados do formulário
para o objeto de modelo, portanto, veja que foi criado o método
getCadastroModel() com esse objetivo. A única novidade lá é que estamos
lendo qual sexo foi selecionado (M ou F). A lógica para ler o valor do
RadioGroup (grupo) é chamar o método getCheckedRadioButtonId() que
retorna um int referente ao id do RadioButton que está selecionado. Para
descobrir qual item está selecionado (M o F), esse id pode ser comparado com
as constantes da classe R, que nesse caso são R.id.radioMasculino e
R.id.radioFeminino, ambas definidas como id do RadioButton lá no XML. Bom,
resumindo, isso foi feito com esta linha de código, sendo assim a variável sexo
dentro da classe de modelo vai conter os valores "M" ou "F" dependendo da
opção selecionada.
model.sexo = if (radioSexo.getCheckedRadioButtonId() == R.id.radioMasculino) "M" else "F"
Depois de preencher o formulário com todos os dados, a classe
CadastroService é chamada passando como parâmetro a classe de modelo.
Neste momento, estamos logando o objeto de modelo:
Log.d("empresa","Cadastro $model")
Portanto, ao digitar os dados no formulário e clicar no botão cadastrar,
você deverá visualizar o seguinte log no LogCat. Com isso, podemos validar se
o objeto foi devidamente preenchido com os dados digitados.
com.example.helloandroid D/empresa: Cadastro
CadastroModel(nome=Ricardo, login=rlecheta, email=a@a.com, sexo=M)
Para finalizar nosso exercício, a figura a seguir mostra a mensagem de
sucesso de cadastro caso a classe de service retorne true:
Figura 11 – Cadastro
Créditos: khuruzero/Shutterstock.
FINALIZANDO
Nesta aula, aprendemos como tratar os eventos do botão e o método
setOnClickListener. Aproveitamos para revisar o conceito de interfaces e de
como podemos implementá-las utilizando o Kotlin. Mais uma vez, vimos que a
utilização de lambdas simplifica o código e por isso vem se tornando um padrão
de mercado.
Também estudamos sobre o LogCat e aprendemos a debugar o código.
E por último, fizemos exercícios práticos sobre como criar classes para organizar
o código, e de certa forma, já deixamos formulários preparados.
REFERÊNCIAS
LECHETA, R. Android Essencial com Kotlin. 2. ed. 2018.
GUIAS DO DESENVOLVEDOR ANDROID. Disponível em:
<https://developer.android.com/guide/>.