Você está na página 1de 39

PROGRAMAÇÃO IV

AULA 5

Prof. Ricardo Rodrigues Lecheta


CONVERSA INICIAL

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


Nós já aprendemos como criar layouts em XML e como tratar os eventos
nas telas. A estrutura básica de um aplicativo já estudamos, e nesta aula vamos
aprender mais detalhes sobre a classe Activity.
Nos aplicativos Android, tudo gira em torno de uma activity. Ela representa
uma tela do aplicativo, sendo responsável pelo layout e lógica. Nesta aula,
vamos estudar o ciclo de vida de uma activity, que é um dos assuntos mais
importantes para que você seja um desenvolvedor Android de sucesso. Também
vamos estudar como passar parâmetros de uma tela para outra, e detalhes sobre
a classe Intent, que é uma das mais importantes no Android, sendo usada desde
para abrir uma tela do aplicativo até para chamar outros aplicativos como o
browser, Google Maps, câmera etc.

TEMA 1 – CICLO DE VIDA DA ACTIVITY

Chegou o momento de entender um dos assuntos mais importantes sobre


uma activity, que é o seu ciclo de vida. Falando com um português bem claro, o
ciclo de vida consiste em um conjunto de métodos da classe Activity que é
chamado em determinados momentos e representam o estado atual da activity,
por exemplo, se a tela está visível ou parada em segundo plano (background).
Um dos métodos do ciclo de vida nós já estudamos, que é o
onCreate(bundle), chamado uma única vez ao criar a activity e responsável por
fazer a inicialização do layout e algumas configurações. Outro método é o
onDestroy(), que é chamado sempre que a tela é fechada, algo que acontece
quando o usuário clica no botão voltar ou se o método finish() é chamado no
código. Em ambos os casos, a activity é eliminada de memória, e nesse
momento, podemos limpar os recursos e objetos utilizados, caso seja
necessário.
Uma forma simples de entender o ciclo de vida é com um exemplo prático
de uso de GPS no aplicativo. Imagine que o aplicativo precisa obter a localização
do usuário sempre que ele entra em uma determinada tela e depois mostrar um
mapa com esta localização. Porém, sabemos que ao chamar as funções de
GPS, existe um aumento significativo no consumo de bateria. Então imagine que
o aplicativo está mostrando um mapa com a localização do usuário, mas depois
disso, o usuário clica no botão Home para voltar à tela inicial para abrir outro
aplicativo. Neste momento, a activity do mapa terá seus métodos onPause() e
onStop() chamados, pois a activity foi parada e agora está em segundo plano
(background). Esses métodos onPause() ou onStop() podem ser utilizados para
desligar o monitoramento do GPS e economizar recursos e bateria do celular.
Seguindo o raciocínio, quando o usuário voltar ao aplicativo e mostrar
novamente aquele mapa com a localização, o método onResume() será
chamado, o que indica que a tela da activity que possui o mapa está novamente
visível. Neste momento, podemos ligar novamente o GPS.
Acredito que, se você entendeu o que foi explicado acima, já é 90% do
caminho andado, mas, como o tema é um pouco complexo, vamos estudar o
diagrama de fluxo oficial do ciclo de vida de uma activity e cada método em
detalhes.
A figura a seguir demonstra o ciclo de vida completo de uma activity,
exibindo os possíveis estados e a chamada de cada método.

Figura 1 – Activity

Depois de ver o diagrama, leia atentamente o significado de cada método


do ciclo de vida da activity.
● onCreate(bundle): o método onCreate(bundle) é chamado uma única
vez. O objetivo desse método é fazer a inicialização necessária para
executar o aplicativo. Nele, deve-se criar uma view e chamar o método
setContentView(view) para configurar o layout da tela. O objeto bundle
contém os parâmetros enviados para a activity e pode ser nulo. Vamos
estudar sobre passagem de parâmetros em outra aula.
● onStart(): o método onStart() é chamado quando a activity está ficando
visível ao usuário e já tem uma view. Pode ser chamado depois dos
métodos onCreate() ou onRestart(), dependendo do estado do aplicativo.
Este método raramente é usado.
● onRestart(): o método onRestart() é chamado quando uma activity foi
parada temporariamente e está sendo iniciada outra vez. Esse método
raramente é usado.
● onResume(): o método onResume() é chamado quando a activity está no
topo da pilha “activity stack” e, dessa forma, já está executando como a
activity principal e interagindo com o usuário. Podemos dizer que o
método onResume() representa o estado de que a activity está
executando. É importante você entender que, quando o método
onResume() executar, o usuário já estará vendo a tela. Por isso, esse
método é frequentemente utilizado para disparar threads que consultam
os dados em web services ou banco de dados para atualizar as
informações da tela.
● onPause(): sempre que a tela da activity fechar, o método onPause() será
chamado. Isso pode acontecer se o usuário pressionar o botão Home ou
o botão voltar do Android. Ou também pode acontecer se você receber
uma ligação telefônica. Nesse momento, o método onPause() é chamado
para salvar o estado do aplicativo, para que posteriormente, quando a
activity voltar a executar, tudo possa ser recuperado, se necessário, no
método onResume().
● onStop(): o método onStop() é chamado logo depois do método
onPause() e indica que a activity está sendo encerrada e não está mais
visível ao usuário. Depois de parada, a activity pode ser reiniciada, se
necessário. Caso isso ocorra, o método onRestart() é chamado para
reiniciar a activity. Caso a activity fique muito tempo parada em segundo
plano e o sistema operacional do Android precise limpar os recursos para
liberar memória, o método onDestroy() pode ser automaticamente
chamado para remover completamente a activity da pilha.
● onDestroy(): o método onDestroy() literalmente encerra a execução de
uma activity. Ele pode ser chamado automaticamente pelo sistema
operacional para liberar recursos ou pode ser chamado pelo aplicativo
com o método finish() da classe Activity. Depois do método onDestroy()
ter executado, a activity será removida completamente da pilha e seu
processo no sistema operacional também será completamente encerrado.

TEMA 2 – EXEMPLO PRÁTICO SOBRE O CICLO DE VIDA

Para demonstrar as chamadas dos métodos do ciclo de vida, vamos criar


a classe LogActivity, que sobrescreve os principais métodos do ciclo de vida e
escreve um log com a classe LogCat. Esta activity não precisa de XML, portanto,
podemos criá-la como uma classe normal com o wizard > File > New > Kotlin
File/Class, e podemos deixá-la no mesmo pacote onde estão as outras activities.

● LogActivity

package com.example.helloandroid

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log

/* Activity que imprime logs nos métodos de ciclo de vida */


open class LogActivity : AppCompatActivity() {
private val TAG = "android"

// Nome da classe sem o pacote


private val className: String
get() {
val s = javaClass.name
return s.substring(s.lastIndexOf(".")+1)
}
override fun onCreate(bundle: Bundle?) {
super.onCreate(bundle)
Log.d(TAG, className + ".onCreate().")
}
override fun onStart() {
super.onStart()
Log.d(TAG, className + ".onStart().")
}
override fun onRestart() {
super.onRestart()
Log.d(TAG, className + ".onRestart().")
}
override fun onResume() {
super.onResume()
Log.d(TAG, className + ".onResume().")
}
override fun onPause() {
super.onPause()
Log.d(TAG, className + ".onPause().")
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Log.d(TAG, className + ".onSaveInstanceState().")
}
override fun onStop() {
super.onStop()
Log.d(TAG, className + ".onStop().")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, className + ".onDestroy().")
}
}

Importante: sempre que sobrescrever um método da classe Activity,


chame o método da classe-mãe com o super; caso contrário, uma exceção será
lançada em tempo de execução.
No início da classe, foi declarada a propriedade className e foi
sobrescrito o método get() para que ela tenha um retorno, facilitando o seu uso
no código. A sintaxe é um pouco chatinha, mas a única coisa que esse código
está fazendo é retornar o nome da classe da activity, sem o pacote.

Saiba mais

Para mais detalhes sobre como criar propriedades em Kotlin, segue o link
oficial: <https://kotlinlang.org/docs/reference/properties.html>.

Essa classe imprime um log quando os métodos do ciclo de vida são


chamados. O log é criado com a tag android, portanto, é necessário criar um
filtro para essa tag na janela do LogCat, conforme já estudamos. O próximo
passo é alterar a classe MainActivity para ser filha de LogActivity, assim, ela vai
herdar todos os métodos que foram customizados na sua classe-mãe.

● MainActivity.kt

class MainActivity : LogActivity() {

Altere as outras activities, também. Vamos apresentar exemplos com a


activity de Cadastro:

class CadastroActivity : LogActivity() {


Ao executar o projeto no emulador, não estamos interessados no layout
da activity, então, desta vez, vamos analisar somente os logs que mostram as
chamadas para cada método do ciclo de vida.
Na primeira vez que o aplicativo executar, os seguintes logs serão
gerados. Observe que os métodos onCreate(), onStart() e onResume() são
chamados na sequência.

D/android: MainActivity.onCreate().
D/android: MainActivity.onStart().
D/android: MainActivity.onResume().

Agora clique no botão voltar (back) do emulador para sair da activity.


Observe que agora os métodos onPause(), onStop() e onDestroy() foram
chamados para encerrar o ciclo de vida da activity.

D/android: MainActivity.onPause().
D/android: MainActivity.onStop().
D/android: MainActivity.onDestroy().

Nesse caso, como o botão voltar foi pressionado, o sistema operacional


sabe que a activity precisa ser destruída. Por isso, o método onDestroy() foi
chamado, eliminando completamente a activity da memória.
Agora volte à tela inicial do emulador e entre novamente no aplicativo.
Isso vai chamar os métodos onCreate(), onStart() e onResume(). Desta vez,
clique no botão Home para voltar à tela inicial. Observe que, nos logs, os
métodos onPause() e onStop() foram chamados, mas não o método onDestroy().

D/android: MainActivity.onPause().
D/android: MainActivity.onStop().
D/android: MainActivity.onSaveInstanceState().

Isso acontece porque a activity não foi completamente destruída, apenas


retirada do topo da pilha, e agora está parada em segundo plano. O método
onSaveInstanceState() é um caso à parte e vamos estudar depois.
Por último, encontre o ícone do aplicativo na tela inicial e abra o aplicativo
novamente. Isso vai reiniciar a activity, fazendo com que ela volte ao topo da
pilha para ser executada em primeiro plano. Assim, os métodos onRestart(),
onStart() e onResume() são chamados.
D/android: MainActivity.onRestart().
D/android: MainActivity.onStart().
D/android: MainActivity.onResume().

Observe que o método onCreate(bundle) é chamado uma única vez. Se


a activity estiver parada em segundo plano, o método onCreate(bundle) não será
chamado novamente. Já o método onResume() é chamado sempre que a tela
fica visível ao usuário.
Perceba que os métodos onPause() e onStop() são sempre chamados ao
sair da tela. O método onResume() é chamado sempre que iniciar ou voltar para
o aplicativo.
O método onCreate() é chamado apenas na primeira vez que o aplicativo
é criado. Caso a activity seja totalmente destruída, evento que acontece ao clicar
no botão voltar, o método onDestroy() é chamado para eliminar os recursos e
encerrar o ciclo de vida da activity. Outra forma de destruir a activity é chamar o
método finish() programaticamente no código.
Entender o funcionamento do ciclo de vida de uma activity é muito
importante para desenvolver aplicativos no Android. Temos de estar preparados
para ações externas que podem ocorrer, tal como uma ligação telefônica para o
usuário. Essa ação faz com que o sistema operacional do Android interrompa
(pause/stop) a activity atual, colocando-a em segundo plano. Isso é feito porque
o aplicativo nativo da ligação é que vai ocupar o topo da pilha de atividades.
Numa situação como essa, quando a ligação terminar, temos de voltar e executar
o aplicativo de onde ele parou, sem perder nenhuma informação.

2.1 Ciclo de vida ao girar o celular?

Você se lembra que comentamos que ainda estudaríaos o método


onSaveInstanceState(bundle)? Sabe aquele parâmetro bundle que existe no
método onCreate(bundle)? Você já pensou para que ele serve? Pois muito bem,
chegou o momento de obtermos essas respostas.
Ao girar a tela do celular da vertical para a horizontal, o Android vai destruir
a activity atual e recriá-la logo em seguida. Sim, é isso mesmo. O Android faz
isso porque ele precisa recriar todas as views e aplicar espaçamentos e margens
adequadas para a nova orientação (vertical ou horizontal).
Durante esse processo de troca de orientação, o Android vai chamar o
método onSaveInstanceState(bundle) na classe da activity. Esse método
recebe um objeto do tipo android.os.Bundle como argumento que deve ser
utilizado para armazenar os dados em uma estrutura de chave e valor. Para
demonstrar esse comportamento, vamos executar o aplicativo novamente. Ao
fazer isso, a MainActivity é criada e inserida no topo da pilha, conforme mostram
estes logs:

D/android: MainActivity.onCreate().
D/android: MainActivity.onStart().
D/android: MainActivity.onResume().

Agora, pressione os botões do emulador para girar a tela para a horizontal


(na barra lateral que fica na direita do emulador). Caso o emulador não gire,
verifique se a opção auto-rotate está habilitada, pois nas versões mais novas
do Android isso vem desligado por padrão.

Figura 2 – Auto-rotate

Crédito: Khuruzero/Shutterstock.

Os logs a seguir mostram que a activity foi destruída e recriada, mas


durante esse processo, o método onSaveInstanceState(bundle) foi chamado.

MainActivity.onPause().
MainActivity.onStop().
MainActivity.onSaveInstanceState(). // Salva o estado
MainActivity.onDestroy(). // Destrói a activity
MainActivity.onCreate(). // Recria a activity e recupera o estado
MainActivity.onStart().
MainActivity.onResume().

A ideia é que se o aplicativo salvou valores no Bundle (estrutura de chave


e valor) lá no método onSaveInstanceState(bundle), é possível recuperar
esses valores no bundle que vem como parâmetro no método
onCreate(bundle?). É para isso que serve esse bundle no método
onCreate(bundle?). Se for a primeira vez que a activity é executada, o parâmetro
bundle sempre estará nulo, por isso ele contém a sintaxe da interrogação (? –
pode ser nulo). Entretanto, no caso da rotação da tela em que o Android faz esse
processo para recriar a activity, o parâmetro bundle pode estar preenchido com
os dados salvos no método onSaveInstanceState(bundle).
Uma vez que já entendemos que o Android precisa matar a tela para
recriar o layout na vertical ou horizontal, quando que na prática precisamos usar
esses métodos para manter o estado da tela? Bom, imagine que seu aplicativo
fez uma busca em um web service para mostrar uma lista de produtos, aliás,
vamos estudar listas nas próximas aulas. Caso o usuário gire a tela do celular,
sabemos que a tela será destruída e recriada, e nesse caso, vamos perder a
lista. Se não fizermos nada, o aplicativo vai buscar a lista novamente no web
service, porém, para isso, será feita uma nova requisição na internet, algo que
poderia ser evitado. Em vez disso, poderíamos salvar a lista dentro daquele
bundle (HashTable), e depois podemos recuperar essa lista já pronta quando o
método onCreate(bundle) for chamado.
Como ainda não estudamos listas, vamos implementar um exemplo mais
simples para que você consiga visualizar tudo isso funcionando na prática. A
ideia é salvar uma variável inteira chamada count na classe. E a cada vez que a
tela for rotacionada, vamos incrementar o valor dessa variável e salvá-la no
bundle. A seguir, podemos ver a alteração de código necessária na
MainActivity. Observe que estão omitidas as demais partes do código para
simplificar o exemplo:

class MainActivity : LogActivity() {


private var count = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if(savedInstanceState != null) {
// recupera o count
Log.d("android","Recuperando estado")
count = savedInstanceState.getInt("count")
}
Log.d("android","Count: $count")
. . .
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Log.d("android","Salvando estado")
count++
outState.putInt("count",count)
}
. . .
}

Ao executar o projeto, a activity será iniciada e os métodos do ciclo de


vida serão chamados. Neste momento, o valor da variável count é 0 (zero).

MainActivity.onCreate().
Count: 0
MainActivity.onStart().
MainActivity.onResume().

Ao girar a tela do emulador, a activity será destruída conforme já


estudamos, e o método onSaveInstanceState(bundle) vai incrementar o valor
da variável count e salvar o estado. Quando o método onCreate(bundle) for
chamado novamente, a variável count estará lá. Neste exemplo, se você ficar
rotacionando a tela continuamente, a variável count ficará sendo incrementada
todas as vezes.

MainActivity.onPause().
MainActivity.onStop().
MainActivity.onSaveInstanceState().
Salvando estado
MainActivity.onDestroy(). // Activity foi destruída.
MainActivity.onCreate(). // Activity recriada.
Recuperando estado
Count: 1
MainActivity.onStart().
MainActivity.onResume().

2.2 Ciclo de vida ao navegar entre as telas?

Ao navegar de uma tela para outra chamando método


startActivity(intent), como fizemos com os botões da tela inicial de login,
também chamam os métodos do ciclo de vida. Para exercitar mais uma vez e
consolidar os conceitos aprendidos, abra o aplicativo novamente e fique olhando
os logs. Depois, clique em algum dos botões como o de cadastro e veja os logs
novamente. Podemos ver que foram gerados os seguintes logs:

MainActivity.onPause().
CadastroActivity.onCreate().
CadastroActivity.onStart().
CadastroActivity.onResume().
MainActivity.onStop().
MainActivity.onSaveInstanceState().

Claramente podemos ver que a MainActivity foi parada (pause/stop) e a


CadastroActivity foi criada e iniciada com as chamadas dos métodos onCreate
> onStart > onResume. Então lembre-se, caso a MainActivty estivesse usando
recursos como GPS, Bluetooth ou qualquer outro que não seja necessário na
próxima tela, você deve parar estes recursos neste momento.
Agora na tela de cadastro, clique no botão voltar. Desta vez, veremos os
seguintes logs, o que mostra que a tela de cadastro foi destruída, e a
MainActivity foi reiniciada com onRestart > onStart > onResume. Geralmente,
no onResume() é onde colocamos os códigos para reiniciar os recursos que essa
tela precisa, seja ligar GPS, Bluetooth, ou refazer uma busca no web service
para preencher os dados da tela.

CadastroActivity.onPause().
MainActivity.onRestart().
MainActivity.onStart().
MainActivity.onResume().
CadastroActivity.onStop().
CadastroActivity.onDestroy().

Com isso, concluímos nossos estudos sobre o ciclo de vida de uma


activity, então, vamos agora falar de Intents.

TEMA 3 – PASSANDO PARÂMETROS AO NAVEGAR ENTRE TELAS

Na primeira tela do aplicativo, aprendemos que, para abrir uma nova


activity, podemos usar o método startActivity(intent):

startActivity(Intent(this,HomeActivity::class.java))

Para facilitar a leitura do código, vamos declarar uma variável para o


objeto Intent que é passado como parâmetro na chamada do método.

val intent = Intent(this,HomeActivity::class.java)


startActivity(intent)

Mas o que é este objeto intent? Explicando de uma maneira simples, a


intent é o objeto que contém a "intenção" de abrir a tela, e nela podemos
adicionar parâmetros para a próxima tela. Para que você entenda bem como isso
funciona, vamos mais uma vez fazer um exemplo prático.
Lembra quando fizemos o login? Nossa classe LoginService está assim:

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
}
}

Isso significa que ao logar com ricardo/123, um objeto Usuário será criado
e o nome será Ricardo. Crie a combinação que preferir para testar. No próximo
exemplo, nosso objetivo será mostrar o nome do usuário logado na tela da
Home, conforme mostra esta figura:

Figura 3 – Nome de usuário

Crédito: Khuruzero/Shutterstock.

A seguir, podemos ver o código que trata a função de login e faz a


passagem de parâmetro do nome do usuário para a próxima tela. As alterações
estão destacadas em amarelo. Observe que a intent possui o método
putExtras(bundle) que recebe o objeto Bundle, o qual contém os parâmetros.
Mais uma vez, o objeto bundle é uma HashTable e, portanto, possui a estrutura
de chave e valores.

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()
// Valida o login
val service = LoginService()
val user = service.login(login,senha)
if(user != null) {
// Fecha a tela de login
finish()
// Abre a tela da home
val intent = Intent(this,HomeActivity::class.java)
val args = Bundle()
args.putString("nome", user.nome)
intent.putExtras(args)
startActivity(intent)
} else {
// Erro
alert("Login incorreto, digite os dados novamente")
}
}

No código, veja que ao criar o objeto Bundle, foi dado o nome de args
para a variável:

val args = Bundle()

Isso é um padrão bem comum no desenvolvimento, pois neste caso, o


Bundle representa os argumentos (args) que estamos passando como
parâmetro para a próxima activity.
Para finalizar o exemplo, precisamos modificar o layout da HomeActivity
para mostrar um texto no centro da tela, portanto, faça a seguinte alteração no
arquivo activity_home.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"
android:gravity="center">
<TextView
android:id="@+id/tNome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="22sp" />
</LinearLayout>

Observe que foi utilizado um LinearLayot com gravity=center para


deixar o texto centralizado. O id do TextView é tNome, isso significa que
podemos acessar essa view com essa variável no código, que é criada
automaticamente pelo Kotlin Extensions. O código atualizado da HomeActivity
fica assim:

package com.example.helloandroid

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

class HomeActivity : AppCompatActivity() {


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

val args = intent.extras


val nome = args?.getString("nome")
tNome.text = nome
}
}

Destacamos em amarelo o import que deve ser feito do arquivo XML para
que o Kotlin Extensions encontre a variável tNome. Preste atenção e não erre
essa linha. Como é uma boa prática utilizar o mesmo id em diversos arquivos
XML, repare que o id tNome também existe no arquivo de layout XML do
cadastro. Um erro comum é importar o arquivo errado, o qual não está sendo
utilizado nesta tela.
Esse código é simples e está recuperando a mesma intent que é enviada
pela activity. Na segunda linha, a variável nome é lida dos parâmetros que foram
enviados. E na terceira linha, o texto do TextView é atualizado com o nome
enviado como parâmetro. Esperamos que esse exemplo tenha sido simples de
entender. Se você alterar o código da classe LoginService, poderá criar várias
combinações de login e senha para testar e brincar.

3.1 Passando um objeto como parâmetro

No exemplo anterior, vimos como passar parâmetros utilizando o objeto


bundle, mas o código que fizemos tem um problema, pois passamos a variável
"nome" isoladamente, e se for necessário passar outros dados do usuário, como
o email, teremos que criar outra linha para este parâmetro, por exemplo:

val args = Bundle()


args.putString("nome", user.nome)
args.putString("email", user.email)

Isso não é uma boa prática em termos de manutenção de código, pois se


tivermos muitos parâmetros, teremos que adicionar várias linhas e isso pode até
resultar em algum erro de lógica. Então, como podemos resolver esse problema?
É muito simples, o correto é passar o objeto Usuário completo como parâmetro.
Para fazer isso, vamos utilizar o método putSerializable(s) conforme mostrado
a seguir:

// Abre a tela da home


val intent = Intent(this,HomeActivity::class.java)
val args = Bundle()
args.putSerializable("usuario", user)
intent.putExtras(args)
startActivity(intent)

Ao fazer essa alteração, o código não vai compilar, portanto, mantenha a


calma. Provavelmente, aquela variável 'user' ficou vermelha, e se você passar o
mouse em cima dela, o Android Studio vai dizer que ela não é do tipo
Serializable.
Este Serializable é uma interface de marcação do Java que indica que
um objeto pode ser serializado, ou seja, pode ser convertido de objeto para bytes
e vice-versa. A boa notícia é que essa interface não possui nenhum método para
implementar, e justamente por isso falamos que ela é uma interface de
marcação, pois basta implementá-la para “marcar” esse objeto como Serializable
e pronto, o resto o Java faz sozinho. Talvez você possa achar estranho falar de
Java, mas o fato é que o Kotlin consegue chamar todos as classes e códigos do
Java, e vale lembrar também que todo o SDK do Android é feito em Java
internamente. Essa interoperabilidade entre o Kotlin e o Java é um dos grandes
pontos fortes do Kotlin, pois ele traz mais produtividade no desenvolvimento, e
ainda consegue chamar classes do Java.

package com.example.helloandroid.domain

import java.io.Serializable
data class Usuario (
var nome: String,
var email: String
) : Serializable

Note que, como estamos usando uma Data Class, entre parênteses está
o construtor do objeto e lá ficam todos os atributos da classe. Depois dos
parênteses, foi colocada a sintaxe para implementar uma interface.
Com a classe Usuário pronta, o código agora está compilando
normalmente e já podemos alterar a classe HomeActivity para ler o objeto
enviado como parâmetro:

package com.example.helloandroid

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.helloandroid.domain.Usuario
import kotlinx.android.synthetic.main.activity_home.*

class HomeActivity : AppCompatActivity() {


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

val args = intent.extras


val user = args?.getSerializable("usuario") as Usuario
tNome.text = user.nome
}
}

Tenha atenção ao alterar o código e observe que estamos usando o


método getSerializable(). Como esse método retorna um objeto do tipo
Serializable, fizemos o cast para Usuário conforme estudamos nas aulas de
Kotlin.
Como agora a variável “user” é do tipo Usuário, podemos acessar seus
atributos como user.nome e user.email tranquilamente. Se quiser brincar, altere
a linha que configura o texto do TextView para mostrar o nome e e-mail:

tNome.text = "${user.nome} - ${user.email}"

Esperamos que tenha ficado clara a vantagem de passar um objeto inteiro


como parâmetro, ao contrário de passar apenas uma simples variável.
TEMA 4 – INTENTS E PERMISSÕES

Uma Intent é o coração do Android e está presente em todos os lugares,


ela representa uma mensagem do aplicativo para o sistema operacional,
solicitando que algo seja realizado. Portanto, uma intent refere-se à intenção de
realizar uma operação, como, por exemplo, abrir uma tela do aplicativo. Embora
na maioria das vezes usamos intents para esses objetivos mais simples, ela é
muito mais que isso. Com uma intent, podemos abrir telas de outros aplicativos
como o de um email, SMS, mapas etc.
Uma intent também é usada pelo sistema operacional para nos notificar
de que algo aconteceu. Por exemplo, ao receber uma mensagem de push
(aquelas notificações), podemos interceptar essa mensagem e fazer alguma
tarefa. Isso tudo são casos mais avançados e que você vai aprender com o
tempo se mergulhar a fundo no mundo do desenvolvimento para Android.
Nos próximos exemplos, vou mostrar códigos de como criar algumas
intents. Mas antes, vamos criar um novo projeto, assim já aproveitamos para
revisar isso. Crie o projeto pelo Android Studio da mesma forma que mostrado
na primeira aula com o wizard > File > New Project e escolha o template Empty
Activity. No nome do projeto, digite HelloIntents e o resto deixe com os valores
padrão.
No layout XML da activity do arquivo activity_main.xml, deixe o seguinte
layout, com apenas um botão para testarmos as intents.

<?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:gravity="center">
<Button
android:id="@+id/btTeste"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Teste"
android:textSize="22sp" />
</LinearLayout>

Observe que foi dado o id btTeste para o botão, assim, podemos usá-lo
para adicionar um evento e chamar as intents que vamos brincar.

package com.example.helloligacao
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {


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

btTeste.setOnClickListener {
// Código aqui
}
}
}

Pronto! A estrutura de código para os próximos exemplos está criada,


então, vamos continuar.

4.1 Intents simples

A primeira intent que vamos estudar é uma chamada simples que vai abrir
o browser, basta fazer a seguinte alteração no código:

package com.example.helloligacao

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {


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

btTeste.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW,
Uri.parse("https://google.com"))
startActivity(intent)
}
}
}

Feito isso, execute o projeto no emulador. Ao clicar no botão, o browser


será aberto na página do Google, como mostrado na próxima figura.
Figura 4 – Browser

Crédito: Khuruzero/Shutterstock.

No próximo exemplo, vamos passar como parâmetro a localização da


Ópera de Arame em Curitiba. Note que passamos os parâmetros com a URI
geo: seguida da lat/lng e do zoom para o Mapa que neste caso é 15.
Altere o código que chama a intent para ficar assim:

val intent = Intent(Intent.ACTION_VIEW, Uri.parse("geo://@-


25.3848941,-49.2763565,15z"))
startActivity(intent)

Desta vez, o aplicativo do Google Maps será chamado e mostrará a


localização desejada.

Figura 5 – Google Maps

Crédito: Khuruzero/Shutterstock.
Até o momento, estudamos dois exemplos de chamadas de intents que
abrem o browser e o mapa, e vimos o quão útil podem ser essas chamadas.

Saiba mais

Existem várias outras chamadas de intents que podemos fazer e


recomendamos que você leia a documentação oficial sobre Intents Comuns, que
são essas chamadas simples que abrem outros aplicativos. Veja mais no link a
seguir: <https://developer.android.com/guide/components/intents-
common?hl=pt-br>.

4.2 Intents seguras e permissões

Agora vamos mostrar um exemplo mais complexo e já adiantamos que


ele não vai funcionar de primeira e o aplicativo vai travar. Vamos tentar fazer
uma ligação telefônica usando a URI 'tel:número':
Altere o código que chama a intent mais uma vez:

val intent = Intent(Intent.ACTION_CALL,


Uri.parse("tel:987654321"))
startActivity(intent)

Ao chamar essa intent, o aplicativo vai travar conforme o esperado e


teremos esta exceção no LogCat. Tente ler a mensagem passo a passo e com
atenção:

FATAL EXCEPTION: main


Process: com.example.helloandroid, PID: 7822
java.lang.SecurityException: Permission Denial: starting Intent {
act=android.intent.action.CALL dat=tel:xxxxxxxxx

Podemos ver pela mensagem que a permissão foi negada pelo sistema
(Permission Denial). Isso aconteceu porque uma ligação é restrita pelo fato de
que ela pode gerar custos para o usuário. Sendo assim, antes de fazer uma
ligação, precisamos pedir permissão para o usuário com um alerta com este:
Figura 6 – Alerta

Crédito: Khuruzero/Shutterstock.

Antes de aprendermos a fazer isso no código, vamos estudar alguns


conceitos sobre permissões. Primeiramente, vá até a tela de configurações do
aplicativo HelloIntents que acabamos de criar. Nas últimas versões do Android,
isso pode ser feito clicando e segurando no ícone do aplicativo por 2 segundos,
depois selecione a opção App Info. Na tela de configurações, veja que esse
aplicativo não requisitou nenhuma permissão para o usuário.

Figura 7 – Permissões

Crédito: Khuruzero/Shutterstock.
Sendo assim, o que precisamos fazer é configurar o aplicativo para
solicitar esta permissão. A primeira tarefa a fazer é adicionar a tag <uses-
permission> no arquivo de manifesto. Essa tag define as permissões
necessárias para o aplicativo, ou seja, todos aqueles alertas que temos que
mostrar ao usuário para ele autorizar o uso de chamadas restritas. Tenha
atenção, pois a configuração fica antes da tag <application>.

● AndroidManifest.xml

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


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.helloligacao">

<uses-permission android:name="android.permission.CALL_PHONE" />

<application . . .>
. . .
</application>

</manifest>

Pronto, feito isso, execute o projeto novamente no emulador.


O resultado é que o aplicativo vai continuar travando. Mas a boa notícia é
que, ao voltar na tela de configuração do aplicativo, você verá que desta vez
existe uma permissão que ainda não foi autorizada, conforme podemos verificar
na próxima figura.

Figura 8 – Permissão não autorizada

Crédito: Khuruzero/Shutterstock.
Para fazermos o primeiro teste e validar a intent que faz a ligação, clique
na permissão de telefone (Phone) e depois clique em Allow para aceitar essa
permissão.

Figura 9 – Aceitar permissão

Crédito: Khuruzero/Shutterstock.

Muito bem. Acabamos de fazer um artifício técnico, conhecido


popularmente como gambiarra, para conceder uma permissão para o aplicativo.
Isso significa que já podemos fazer a ligação utilizando aquela intent.
Desta vez, ao clicar no botão que chama a intent, o aplicativo que faz as
chamadas telefônicas será aberto e vai iniciar a ligação. Legal, não é mesmo?

Figura 10 – Ligação

Crédito: Khuruzero/Shutterstock.
4.3 Solicitando as permissões pelo código

O exemplo da ligação funcionou e foi importante para entendermos o que


é uma permissão, mas é claro que o correto é solicitar as permissões dentro do
aplicativo, e é exatamente isso que vamos fazer no próximo exemplo.
Para continuar, entre na tela das permissões novamente e, desta vez,
clique em Deny para negar a permissão e voltar ao estado inicial do aplicativo.

Figura 11 – Negar permissão

Crédito: Khuruzero/Shutterstock.

Feito isso, crie a classe PermissionUtils no projeto com o seguinte


código. Veja que a classe foi anotada com a palavra reservada object, portanto,
ela é um singleton e pode ser utilizada com a notação Classe.método(),
conforme estudamos na aula de Kotlin.

package com.example.helloligacao

import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat

object PermissionUtils {
// Solicita uma permissão se ainda não foi concedida
fun request(activity: Activity, permissions: Array<String>) :
Boolean {
val ok = validate(activity, permissions)
if (!ok) {
ActivityCompat.requestPermissions(
activity,
permissions,
1
)
}
return ok
}
// Valida se as permissões foram concedidas
fun validate(context: Context, permissions: Array<String>):
Boolean {
for (permission in permissions) {
val result = ContextCompat.checkSelfPermission(context,
permission)
if (result != PackageManager.PERMISSION_GRANTED) {
return false
}
}
return true
}
}

Com o tempo, você vai estudar e aprender mais sobre o código contido
nessa classe. Por enquanto, vamos aprender a utilizá-la, sendo assim, faça as
seguintes alterações no código e leia os comentários com atenção, pois as
explicações estão no código.

package com.example.helloligacao

import android.Manifest
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {


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

btTeste.setOnClickListener {
val intent = Intent(Intent.ACTION_CALL,
Uri.parse("tel:987654321"))
startActivity(intent)
}

// (1): Esse método valida se a permissão já foi concedida.


// Caso o usuário já tenha aceitado, ele retorna true.
// Caso contrário, retorna false e mostra o alerta ao usuário.
val ok = PermissionUtils.request(this,
arrayOf(Manifest.permission.CALL_PHONE))

if(!ok) {
// Se o usuário ainda não aceitou a permissão ou se ele
foi negada...
// Deixa o botão Invisível
btTeste.visibility = View.INVISIBLE
}
}

override fun onRequestPermissionsResult(


requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions,
grantResults)
val ok = PermissionUtils.validate(this, permissions)
if(ok) {
// Deixa o botão visível se o usuário aceitou a permissão
btTeste.visibility = View.VISIBLE
}
}
}

O objetivo deste código é mostrar o alerta de permissão para o usuário.


Isso é feito nesta linha:

val ok = PermissionUtils.request(this,
arrayOf(Manifest.permission.CALL_PHONE))

Caso o método retorne false, sabemos que o alerta foi mostrado ao


usuário e o aplicativo está aguardando se ele vai aceitar ou não a permissão.
Por isso, nesse momento, o botão é deixado invisível, pois não podemos deixar
o usuário clicar nele antes de aceitar a permissão.

btTeste.visibility = View.INVISIBLE

Figura 12 – Permissão

Crédito: Khuruzero/Shutterstock.
Quando o usuário responder, o método onRequestPermissionsResult()
é chamado para validar as permissões, algo que estamos fazendo com esta linha
de código:

val ok = PermissionUtils.validate(this, permissions)

E por fim, caso o usuário tenha aceitado a permissão, o botão é deixado


visível novamente, o que significa que o usuário autorizou este aplicativo a fazer
ligações telefônicas.

btTeste.visibility = View.VISIBLE

Conforme vimos, controlar as permissões do aplicativo não é uma tarefa


muito simples, é um trabalho bem delicado e que exige um certo refinamento.
Neste exemplo simples, o botão apenas foi deixado invisível, mas dependendo
do caso, o tratamento poderia ser mostrar outro layout no lugar do botão. O
interessante é que aprendemos a usar a propriedade visibility da View, e com
ela, podemos informar se uma view está visível ou invisível.
Como desenvolvedor, você precisa trabalhar a interface do aplicativo para
não permitir que o usuário chame alguma funcionalidade restrita se ele ainda
não aprovou a permissão, pois se o fizer, o Android lançará uma exceção.
Caso tenha um celular Android em mãos, uma sugestão é você entrar na
tela de permissões do WhatsApp e depois negar todas as permissões. Ao voltar
ao WhatsApp, tente chamar alguma funcionalidade restrita como gravar um
áudio ou tirar uma foto. Você verá que o WhatsApp mostra uns alertas elegantes
para o usuário, ou seja, ele faz o controle se as permissões estão aceitas e as
trata devidamente.

TEMA 5 – TIRANDO UMA FOTO

Para finalizar nossos estudos sobre uma intent, vamos fechar com chave
de ouro e tirar uma foto com a câmera pelo aplicativo. Da mesma maneira que
abrimos o browser e o Google Maps e depois também fizemos uma ligação,
podemos usar uma intent para abrir o aplicativo da câmera. A diferença é que o
aplicativo da câmera vai nos retornar a foto que foi tirada pelo usuário, portanto,
temos que aguardar esse retorno.
Atenção: este exemplo é mais complexo e vai exigir mais de você. Nele,
vamos revisar quase tudo o que já aprendemos, portanto, leve o tempo que
precisar para estudar o código com calma.
Para esta brincadeira, vamos criar um novo projeto chamado
HelloCamera, da mesma forma que fizemos nos exemplos anteriores. Altere o
layout da activity para ter um botão na parte superior, e uma imagem logo abaixo.
O objetivo é clicar o botão para tirar a foto e depois mostrá-la no ImageView.

● 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"
android:orientation="vertical"
android:padding="16dp">

<ImageButton
android:id="@+id/btAbrirCamera"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="center"
android:src="@android:drawable/ic_menu_camera"
android:text="Abrir a Câmera" />

<ImageView
android:id="@+id/imgFoto"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center"
android:layout_weight="1" />
</LinearLayout>

O interessante é que no layout XML criamos um botão com o


ImageButton, ou seja, é um botão que vai mostrar uma imagem de uma câmera.
Para isso, usamos um recurso nativo do Android que já tem essa figura:
@android:drawable/ic_menu_camera. Observe também que as duas views
possuem ids, portanto, podemos acessá-las no código.
Com o layout criado, vamos continuar. Primeiro, vamos digitar todos os
arquivos necessários para o exemplo funcionar, e depois vamos estudar o
código, combinado?
A seguir, temos o código da activity que dispara a intent para abrir a
câmera. O código está comentado, leia com atenção.
● MainActivity.kt

package com.example.hellocamera

import android.Manifest
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File

class MainActivity : AppCompatActivity() {


// Caminho para salvar o arquivo
var file: File? = null

public override fun onCreate(savedInstanceState: Bundle?) {


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

btAbrirCamera.setOnClickListener {
onClickCamera()
}
if (savedInstanceState != null) {
// (*2*) Se girou a tela recupera o estado
file = savedInstanceState.getSerializable("file")
as File
showImage(file)
}

// Valida a permissão para Câmera


val ok = PermissionUtils.request(this,
arrayOf(Manifest.permission.CAMERA))
if(!ok) {
// Deixa o botão Invisível
btAbrirCamera.visibility = View.INVISIBLE
}
}

// Cria um arquivo no sdcard privado do aplicativo


// A câmera vai salvar a foto no caminho deste arquivo
fun createFile(fileName: String): File {
val sdCardDir =
getExternalFilesDir(Environment.DIRECTORY_PICTURES)
if (sdCardDir != null && sdCardDir.exists()) {
sdCardDir.mkdir()
}
val file = File(sdCardDir, fileName)
// Salva file como atributo para salvar estado caso
gire a tela
this.file = file
return file
}

private fun onClickCamera() {


// (*1*) Cria o caminho do arquivo no sdcard
val file = createFile("foto.jpg")
log("Camera file: $file")
// Chama a intent informando o arquivo para salvar a
foto
val i = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
val uri = FileProvider.getUriForFile(
this,
applicationContext.packageName + ".provider", file
)
i.putExtra(MediaStore.EXTRA_OUTPUT, uri)
// Chama a intent de captura de foto
startActivityForResult(i, 0)
}

override fun onActivityResult(requestCode: Int, resultCode:


Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
// (*4*) Se a câmera retornou, vamos mostrar o
arquivo da foto
showImage(file)
}
}

// Atualiza a imagem na tela


private fun showImage(file: File?) {
if (file != null && file.exists()) {
// (*5*) Redimensiona a imagem para o tamanho do
ImageView
val bitmap = ImageUtils.resize(file, 1200, 800)
log("img1: w/h:" + imgFoto.width + "/" +
imgFoto.height)
log("img2: w/h:" + bitmap.width + "/" +
bitmap.height)
log("file:" + file)
// (*6*) Mostra a foto no ImageView
imgFoto.setImageBitmap(bitmap)
}
}

fun log(s: String) {


Log.d("android", s)
}

override fun onSaveInstanceState(outState: Bundle) {


super.onSaveInstanceState(outState)
// (*3*) Salvar o estado caso gire a tela
outState.putSerializable("file", file)
}

override fun onRequestPermissionsResult(


requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode,
permissions, grantResults)
val ok = PermissionUtils.validate(this, permissions)
if(ok) {
// Deixa o botão visível se o usuário aceitou a
permissão
btAbrirCamera.visibility = View.VISIBLE
}
}
}

Como já estudamos o que é uma intent, permissões e o método


onSaveInstanceState(bundle), a maior parte do código você deverá entender
sem problemas. Observe que logo no início do código estamos solicitando a
permissão para utilizar a câmera. Caso a permissão seja negada, o botão ficará
invisível.

val ok = PermissionUtils.request(this,
arrayOf(Manifest.permission.CAMERA))

A classe PermissionUtils é exatamente a mesma que usamos no


exemplo da ligação telefônica, é só copiar para este projeto. E para a permissão
funcionar, adicione no arquivo de manifesto a seguinte configuração:

● AndroidManifest.xml

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


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hellocamera">

<uses-permission android:name="android.permission.CAMERA" />

<application >
. . .
</application>

</manifest>

Mas para o código compilar, ainda precisamos criar o arquivo ImageUtils,


que vai fazer o dimensionamento da foto antes de mostrar no ImageView, pois
as câmeras, atualmente, tiram fotos com muitos pixels, e para economizar
memória, os aplicativos fazem este redimensionamento.
O código desta classe está com comentários em inglês, pois foi tirado da
documentação oficial 1.

● ImageUtils.kt

package com.example.hellocamera

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import java.io.File

object ImageUtils {
// Faz o resize da imagem
fun resize(file: File, reqWidth: Int, reqHeight: Int):
Bitmap {
// Verifica as dimensões do arquivo
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(file.absolutePath, options)
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options,
reqWidth, reqHeight)
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false
return BitmapFactory.decodeFile(file.absolutePath,
options)
}
// Calcula o fator de escala
fun calculateInSampleSize(options: BitmapFactory.Options,
reqWidth: Int, reqHeight: Int):
Int {
// Raw height and width of image
val height = options.outHeight
val width = options.outWidth
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight = height / 2
val halfWidth = width / 2
while
(halfHeight / inSampleSize >= reqHeight &&
halfWidth / inSampleSize >= reqWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
}

Pronto, estamos quase terminando, falta apenas uma configuração. Saiba


que, quando chamarmos a intent para abrir a câmera, vamos passar como

1
Disponível em: <https://developer.android.com/training/camera/photobasics>. Acesso em: 22
jan. 2021.
parâmetro o caminho de um arquivo para que a foto seja salva. Esse arquivo
ficará numa pasta privada do nosso aplicativo:

/storage/emulated/0/Android/data/com.example.hellocamera/files/Pictur
es/foto.jpg

Mas, por questões de segurança, a partir do Android 7 (Nougat), o Android


não permite que a câmera ou qualquer outro aplicativo salve arquivos nessa
pasta sem darmos acesso. Portanto, as configurações que vamos fazer agora é
dar acesso ao aplicativo da câmera para gravar arquivos na pasta privada do
nosso aplicativo.
Para isso, crie uma pasta /res/xml conforme indicado na figura e o arquivo
provider_paths.xml.

● /res/xml/provider_paths.xml

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


<paths>
<external-path name="external_files" path="."/>
</paths>

Figura 13 – Pastas

Por último, temos que criar esta configuração no arquivo de manifesto.


● AndroidManifest.xml

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


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hellocamera">

<uses-permission android:name="android.permission.CAMERA" />

<application . . . >
<activity android:name=".MainActivity" . . . >

<!-- Permite a Câmera salvar fotos no nosso app -->


<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>

</application>
</manifest>

Pronto, terminamos! Vamos ver o projeto funcionando?


Ao executar o projeto no emulador, o primeiro passo é solicitar ao usuário
a permissão para utilizar a câmera.

Figura 14 – Permissão

Crédito: khuruzero/Shutterstock.
Essa parte do código é exatamente igual ao exemplo anterior que fizemos
e não iremos explicá-la, portanto, revise o código com atenção.
Ao clicar no botão para tirar uma foto, o método onClickCamera() é
chamado. Nele, veja que criamos o arquivo no qual queremos que a foto seja
salva. Logo depois, é criada uma intent com ação
MediaStore.ACTION_IMAGE_CAPTURE, que será interceptada pela câmera.
Você verá no código que ao criar a intente, é usado o parâmetro
MediaStore.EXTRA_OUTPUT para informar o caminho do arquivo que a
câmera vai receber como argumento.
Feito isso, a câmera será chamada e você poderá tirar uma foto, conforme
mostra a figura. Observação: o emulador possui um bug em que, na primeira
vez, a foto não funciona. Se isso acontecer, clique no botão voltar para fechar a
câmera e tente novamente.

Figura 15 – Câmera

Crédito: khuruzero/Shutterstock.

Depois de tirar a foto, clique no botão de confirmar dentro do aplicativo da


câmera. Isso vai fechar a câmera e devolver o arquivo para nosso aplicativo. O
resultado é que o método onActivityResult() será chamado. Ele é um método
que é chamado quando um aplicativo retorna dados para outro. Nós não
havíamos comentado, mas veja que, ao chamar a intent da câmera, foi utilizado
o método startActivityForResult() justamente por isso. Veja no método
onClickCamera().
O método onActivityResult() contém a lógica necessária para mostrar a
foto no ImageView. Veja o código do método showImage(file). Nele, estamos
chamando a classe ImageUtils para fazer o redimensionamento da foto,
conforme já explicado. O resultado depois de tirar a foto podemos ver na figura
a seguir:

Figura 16 – Foto

Crédito: khuruzero/Shutterstock.

Por último, faça um teste e gire a tela do emulador para horizontal. O


resultado é que a foto é salva e não perdemos o estado do aplicativo. Isso só foi
possível porque o método onSaveInstanceState(bundle) foi implementado,
conforme já estudamos. Nele, salvamos o arquivo "file" e, depois, quando o
Android recriou a activity, lá no método onCreate(bundle), esse arquivo foi
recuperado e mostrado automaticamente na tela.

Figura 17 – Tela na horizontal

Crédito: khuruzero/Shutterstock.
FINALIZANDO

Nesta aula, estudamos o ciclo de vida de uma activity e depois


aprendemos conceitos importantes sobre uma intent, a passagem de parâmetros
entre telas e o controle de permissões paras as intents restritas. Por fim,
aprendemos a tirar uma foto com a câmera.
REFERÊNCIAS

ANDROID DEVELOPERS. Documentação oficial - Intents comuns.


Disponível em: <https://developer.android.com/guide/components/intents-
common?hl=pt-br>.

_____. Documentação oficial – Câmera. Disponível em:


<https://developer.android.com/training/camera/photobasics>.

_____. Android Training – Loading Large Bitmaps Efficiently. Disponível


em: <https://developer.android.com/topic/performance/graphics/load-
bitmap.html>.

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

Você também pode gostar