Baixe o app para aproveitar ainda mais
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/>.
Compartilhar