Olá à todos.
Neste post começaremos a explicar como interagir com Banco de Dados em Android.
Em Android, o SGBD (Sistema Gerenciador de
Banco de Dados) utilizado é o
SQLite.
O SQLite
é um banco de dados gratuito (Open Source).
O SQLite possui características padrões à um banco de dados relacional, como a
sintaxe SQL, transações e “prepared statements”. Seu banco de dados requer
memória limitada em tempo de execução (aproximadamente 250 Kbytes), o que o
torna um bom candidato para ser utilizado em dispositivos mobile.
O SQLite
suporta os tipos TEXT (similar à String em Java), INTEGER (similar à Long em
Java) e REAL (similar à Double em Java). Quaisquer outros tipos devem ser convertidos
em algum destes, antes de serem salvos no banco de dados. O SQLite não valida se os tipos escritos
nas colunas são de um tipo definido. Ou seja, podemos escrever um Integer em uma coluna String, e vice-versa.
Mais informações sobre o SQLite podem ser encontradas no site http://www.sqlite.org.
O SQLite
fica armazenado internamente em cada dispositivo Android. Usar SQLite em Android não requer
administração de seu banco de dados. Apenas temos que definir procedimentos SQL
para criar e atualizar o banco de dados. Após isto, o banco de dados é
automaticamente gerenciado pela plataforma Android.O acesso a um banco de dados SQLite envolve acessar o sistema de arquivos.
Isto pode ser bem lento. Por este motivo, é recomendado realizar estas
operações de forma assíncrona.Se sua aplicação cria um banco de dados, este
banco por default será salvo no diretório DATA/data/APP_NAME/databases/FILENAME.
As partes do diretório acima são construídas
baseadas nas seguintes regras:
- DATA
é o diretório ao qual o método Environment.getDataDirectory()
retorna.
- APP_NAME
é o nome da sua aplicação.
- FILENAME
é o nome que você especificou para o banco de dados da sua aplicação.
Neste post aprenderemos a implementar um banco
de dados em Android de diversas formas:
- Básico,
utilizando comandos SQL inteiros através do método execSql().
- Avançado,
utilizando ContentValues (que é o
método aconselhado pela Google).
- Utilizando
Frameworks, que reduzem bastante nosso trabalho. Utilizaremos o ORMLite.
Implementaremos um CRUD (Create, Read, Update e Delete). Ou
seja, implementaremos as operações básicas de SQL.
Banco de Dados Básico (execSql)
Para esta primeira metodologia, utilizaremos
como exemplo um aplicativo de perguntas e respostas, um “Quizz”.
Nele conterão perguntas e respostas sobre
Geografia. Serão 10 no total, e ao fim delas, será dado o resultado do Quizz ao
usuário. Então, de onde começamos a implementação deste aplicativo?
Podemos concluir que este projeto em Android será
dividido da seguinte forma:
- Pacote
com o modelo de dados. Ou seja, as classes que contém as entidades de Perguntas
e Respostas. Um conceito bem conhecido sobre entidades representando dados de
um banco de dados é o Bean. Por isto,
criaremos um pacote chamado bean.
- Pacote
com as Activity’s, ou seja, as telas
do aplicativo. Chamaremos este pacote de view.
- Pacote
com os métodos de manipulação do banco de dados. Para este exemplo,
desmembraremos este pacote em dois diferentes pacotes:
- O
primeiro conterá apenas a definição dos métodos, com intuito didático.
Chamaremos este pacote de dao.
- O
segundo conterá a implementação dos métodos do pacote acima. Chamaremos este
pacote de impl.
Começaremos a demonstração deste aplicativo
pelas classes do pacote bean.
Este pacote conterá as seguintes classes: Disciplina.java:
E
Pergunta.java.
Podemos notar nestas classes algumas coisas em
comum: ambas possuem os campos (que associaremos ao banco de dados) definidos
como atributos, um construtor com argumentos e um construtor sem argumentos, um
método toString(), e os métodos get() e set(). Desta forma, os objetos Pergunta e Resposta possuem tudo que
precisamos para os utilizarmos nos pacotes view
e impl.
Já no pacote dao temos os protótipos de métodos que
farão a interação do aplicativo Android com o banco de dados. Seu nome é BancoDao.java.
Nela podemos ver alguns métodos genéricos (que
funcionam tanto para Perguntas como Respostas), métodos específicos (como pesquisaPergunta() e insereResposta()), e métodos
obrigatórios em qualquer conexão com banco de dados (criaConexao() e fechaConexao()).
Alguns métodos retornam um objeto do tipo Cursor. O que é um Cursor? Um Cursor se
assemelha à um PreparedStatement,
bastante utilizando em Java JDBC. Ele recebe o retorno de uma query de banco de dados.
Para entender melhor todos os métodos acima,
precisamos dar uma olhada nas implementações destes.
Tais implementações estão no pacote impl. Este pacote possui apenas uma classe, chamada BancoDaoImpl.java. Ela possui mais de
200 linhas! Por ser uma classe extensa, ela será mostrada aqui em partes.
O início desta classe é mostrado abaixo. Nele
temos:
- CATEGORIA → Constante à ser utilizada pelos log()
desta classe. A classe Log é uma
classe que mostra ao usuário mensagens sobre o aplicativo. Elas podem ser de 5
tipos: d (debug), e (error), i (info), v (verbose),
w (warn).
- context → Variável
que contém o contexto que utilizaremos para interagir com o banco de dados.
- db → Variável
que contém a instância do banco em nossa aplicação Android.
- sql → Variável do tipo String que contém os comandos SQL à serem utilizadas nesta classe.
- BANCO → Constante que define o nome do banco
que criaremos.
- TABELA_DISCIPLINA → Constante que possui o comando
SQL para criar a tabela Disciplina. Esta tabela não foi referenciada
anteriormente pois utilizaremos apenas uma disciplina em nosso exemplo:
Geografia.
- TABELA_PERGUNTA → Constante que possui o comando
SQL para criar a tabela Pergunta.
- TABELA_RESPOSTA → Constante que possui o comando
SQL para criar a tabela Resposta.
Seguindo a
explicação da nossa classe
BancoDaoImpl.java,
temos:
Acima temos 5 métodos. Vamos à eles:
- BancoDaoImpl() → É o
construtor da nossa classe. Nele o contexto que utilizaremos para acessar o
banco de dados é setado.
- criaConexao() → Função fundamental em qualquer aplicativo que utiliza bancos de dados.
Com ela criamos a conexão com o banco. Isto é feito pelo método context.openOrCreateDatabase().
- deletaRegistros() → Um
dos 3 métodos genéricos da imagem acima. Neste métodos todos os registros do
banco de dados são removidos. Isto é feito de forma manual através de 3 SQL’s
que deletam as tabelas Disciplina, Pergunta e Resposta. Repare que o método
mais importante de todos (que é o método que diretamente executa uma query no banco de dados) é o método execSQL()).
- criaTabelas() → Este
é o método que será chamado na Activity
que utilizar acesso ao banco de dados. Ele faz tudo que precisamos para começar
a manipular dados do nosso banco. À cada acesso ao aplicativo, este método será
acessado, e com ele criaremos uma conexão e atualizaremos os dados do banco
(deletando os registros antigos, criando as tabelas novamente, e populando-as).
- criaTabela() →
Neste método passamos um SQL como argumento, que criará as tabelas que
utilizaremos em nosso aplicativo.
Agora seguiremos a classe
BancoDaoImpl.java mostrando como operações de CRUD são realizadas,
através da interação de cursores e métodos de acesso direito ao banco de dados,
como
query() e
execSQL().
Na sequência da nossa classe, temos os métodos:
populaTabelaPergunta(), pesquisaPergunta(), pesquisaResposta(), insereResposta(),
listarPerguntas(), listarRespostas() e fechaConexao(). Relembrando as operações CRUD: os métodos criaTabela() e populaTabelaPergunta() implementam o “C” do CRUD (Create); os métodos pesquisaPergunta(), pesquisaResposta()
e listarPerguntas() implementando o
“R” (Read); nenhum método implementan
o “U” (Update); e nenhum método
implementando o “D” do CRUD (Delete).
O método fechaConexao() não é um
método de CRUD, mas sim um método que gerencia o acesso ao banco de dados.
Na figura abaixo, não mostraremos todos estes
métodos. Mostraremos apenas um exemplo da operação “Create” e um exemplo da operação “Read”. Algo bem básico.
Acima temos o método insereResposta() (implementando a operação “Create”) e os métodos pesquisaPergunta()
e listarPerguntas() (implementando a
operação “Read”). Vamos à eles:
- O
método pesquisaPergunta() recebe como parâmetro o id da pergunta à ser
pesquisada, e retorna um Cursor.
Neste método, mais uma vez, o primeiro comando executado é a abertura do banco
de dados através do método context.openOrCreateDatabase().
Após isto, o comando SQL foi executado através do método query(). O construtor do método query() é: public
Cursor query (boolean distinct, String table, String[] columns, String
selection, String[] selectionArgs, String groupBy, String having, String
orderBy, String limit). No nosso método pesquisaPergunta() temos: query(true, "pergunta", colunas,
where, null, null, null, null, null). Desta forma:
- O
parâmetro distinct recebeu true. Logo, se existir mais de um
registro repetido na tabela, mais de um registro será retornado.
- O
parâmetro table recebeu “pergunta”. Logo, passamos à query a tabela Pergunta.
- O
argumento columns recebeu colunas. Como podemos ver na imagem
acima: String[] colunas = {"idPergunta", "idDisciplina",
"pergunta", "respostaCorreta"};. Logo, estamos
passando à query todas as colunas que
serão retornadas pelo banco de dados.
- O
parâmetro selection recebeu where. Na imagem acima vemos que String
where = "idPergunta = " + idPergunta;. Assim, estamos
passando à query o critério de
seleção para buscar dados na tabela Pergunta.
- Os
demais 5 parâmetros são null pois já
temos tudo que precisamos para nossa query
retornar os dados desejados.
- O
método insereResposta() utiliza o
método execSQL() para realizar à
operação SQL. O que muda neste método em relação ao método criaTabela() (muito similar ao insereResposta())
é que neste método a variável sql
montada utilizando o parâmetros do nosso método, a fim de insermos apenas uma
resposta no banco de dados (ou seja, inserir a resposta passando como
parâmetro).
- O método listarPerguntas() também utiliza
o método query(). Os 3 primeiros
parâmetros deste método são análogos aos do método pesquisaPergunta(). Porém, o 4º parâmetro é diferente. O parâmetro selection,
neste caso, é nulo. Ou seja, todos os registros serão retornados. Também o 8º
método é diferente. Neste caso, o parâmetros orderBy recebe o valor “idPergunta”.
Logo, todas as perguntas retornadas pelo banco de dados serão ordenadas pelos
seus id.
A maneira com que os cursores são lidos será
especificada mais a frente.
Por fim, temos o método fechaConexao().
Este método finaliza a conexão com o banco de
dados SQLite. É IMPORTANTÍSSIMO que este
método seja chamado, senão poderemos ter casos de estouro de memória utilizada.
Por fim, falaremos do pacote view. Neste pacote temos todas as Activity’s (telas) que serão utilizadas
pelo aplicativo. Serão 4 no total: LoginActivity.java,
TelaInicial.java, QuizzGeografia.java e PontuacaoFinal.java.
Então, por qual tela começamos? Para isso,
precisamos dar uma olhada no arquivo AndroidManifest.xml.
Assim, podemos concluir que a primeira tela à
ser aberta por nosso aplicativo será a tela LoginActivity.java.
Esta tela será explicada em detalhes, abaixo. Ela não contém explicações sobre
o conteúdo deste post, mas ela será destrinchada por se caracterizar como
uma classe útil para fazermos uma revisão de tudo que foi visto no Blog até
agora.
Logo abaixo, temos o Javadoc da nossa classe. O Javadoc
é a documentação que identifica uma classe (e também métodos, variáveis, etc, como
mostraremos mais adiante neste post).
Agora, temos a definição da nossa classe como
uma Activity. Note que ela implementa
um ClickListener, e que possui as
variáveis mEdtUsuario e mBtnIniciar.
Repare também que estas variáveis (que são
atributos de classe) começam com uma letra “m”. Por que? Porque em Java temos
padrões de nomeação de variáveis. São eles:
- Variável → TODA variável em Java deve
começam em letras minúsculas, e caso seu nome possua mais de um termo ou
palavra, este deve possui apenas uma letra maiúscula: a primeira letra do
termo/palavra. Exemplo: String umaString;
- Atributo
de classe → Coloca-se um prefixo com a letra “m”. Exemplo: private EditText mEdtUsuario;
- Constante → Toda
constante em Java, além de ser definida como um final (variável com valor imutável), deve ser maiúscula. Exemplo: public static final String CONSTANTE =
“constante”;
- Estáticos → Uma variável
estática é uma variável que é mantida na memória ao longo de todo o tempo de
vida do programa. E ao contrário de uma constante, ela é mutável. Exemplo: public static String sNomeDoPrograma =
“nome_do_programa”;
Tais padrões se extendem, também, para o
desenvolvimento Android.
Continuando a explicação da nossa classe, temos
os eventos onCreate() e onDestroy(). Neles temos comandos de log e de criação da tela da nossa LoginActivity.java (através do comando setContentView() do evento onCreate()).
Em sequência, temos mais eventos de uma classe Activity. De mais interessante, podemos
enaltecer os métodos setResult(RESULT_CANCELED)
e finish(). O primeiro método se
relaciona com o que foi ensinado neste post do Blog; e o segundo sai da tela atual (e que por ser a primeira da nossa
aplicação, corresponde também à saída da aplicação).
Já na
figura abaixo, temos um código mais interessante. No evento onResume() (que é chamado sempre que o
contexto da aplicação entra em uma tela, seja pela primeira vez, ou seja
retornando à ela), temos, além dos comando de log, a inicialização do listener
do botão. Mas apenas isso. Onde teremos então a ação que tal botão realizará?
A ação do botão é mostrada abaixo, no evento onClick(). Nela verificamos se o login
do usuário não é nulo. Se não for, o contexto da aplicação é desviado para a
tela
TelaInicial.java, e também o
nome do usuário é passado para esta tela como um
extra da
Intent chamada.
Por fim, na tela LoginActivity.java, temos um método (com seu devido Javadoc) que
obtém o nome da classe em que o contexto da aplicação se encontra. Esse método
foi utilizado em nossa classe pelos comandos de log.
Pudemos notar que este login é extremamente
simples. Ele não possui senha e quase nenhuma validação de campos. Isso foi
feito propositalmente, para facilitar na didática e apresentação de conceitos
iniciais em programação Android.
Agora que já reforçamos bastante muitos
conceitos ensinados nesta apostila (Ciclo de Vida de uma Activity, Conceitos de Interface, Intent, dentre outros), vamos destrinchar a tela QuizzGeografia.java. Por que esta tela,
e não a tela TelaInicial.java, que é
a próxima tela que a aplicação acessará? Porque a tela TelaInicial.java possui uma estrutura parecida com a tela LoginActivity.java, e nosso foco agora
será explicar a relação do pacote view
com os pacotes impl e bean (o que é feito na classe QuizzGeografia.java).
Abaixo temos a classe QuizzGeografia.java. Para entendê-la bem, podemos dar uma conferida
nas variáveis que esta classe possui.
Podemos perceber que existem variáveis
utilizadas na manipulação de bancos de dados, e também variáveis de componentes
gráficos da classe. Mais adiante, temos os eventos de ciclo de vida da tela.
Dentre eles, o mais importante é o
onResume().
Repare que é neste evento que o método mDb.criaTabelas() é chamado. Ou seja,
sempre que esta tela for acessada, as tabelas do banco de dados serão geradas.
Temos também o método obtemPergunta(),
que indiretamente faz acesso ao banco de dados, como será mostrado mais
adiante.
Seguindo nossa tela, temos a definição de
alguns métodos usados ao longo dela. O método getClassName() obtém o caminho da classe nos pacotes do aplicativo,
e o método tituloUsuario() apenas
atualiza o nome do usuário na tela.
Agora temos
o método obtemPergunta(). Ele utiliza
o método retornaPergunta(), que de
forma direta obtém as perguntas do banco de dados. Como isto é feito será
mostrado mais adiante.
Agora temos o método acaoBotao(), que encapsula o que será realizado pelo botão de
resposta. Podemos notar que é chamado o método insereResposta(), que insere no banco a resposta que o usuário
escolheu (Sim ou Não), e que logo após disso a conexão com o banco de dados é
fechada. Ela é fechada pois mantê-la aberta é uma má prática, pois manter a
conexão aberta consome recursos do nosso aplicativo.
Repare também que o if deste método controla se será mostrada na tela uma das 10
perguntas, ou se sairemos da tela de Quizz de Geografia (tal ação é executada
no else deste método).
Como podemos perceber acima, foi chamado o
método finalizaTela(). Abaixo temos
sua implementação. Nele, chamamos o método que calcula a pontuação do Quizz, desviamos
o fluxo do aplicativo para a tela PontuacaoFinal.java,
e também fechamos a conexão com o banco de dados.
O método
que calcula a pontuação é mostrado abaixo.
Como mencionado no início deste post, a
leitura de dados de uma tabela de um banco de dados é feita através da classe Cursor. No método acima podemos perceber
que os valores da tabela Pergunta são lidos pelo método listarPerguntas(), e também como colocamos tais valores em variáveis
da nossa classe (que neste método são os arrays Integer[] corretas e Integer[]
usuarios).
Ou seja, tudo que precisamos fazer é percorrer
todos os elementos de um Cursor
através de um comando for each. Desta
forma podemos comparar as respostas corretas com as respostas dadas pelo
usuário, e descobrir quantas respostas o usuário acertou.
Por fim, vamos aos métodos que lêem do banco de
dados a Pergunta e a Resposta de cada uma das 10 questões do nosso Quizz.
Podemos notar que mais uma vez foi utilizada a
classe Cursor. No método retornaPergunta(), cada coluna da tabela
Pergunta é lida e inserida em algum atributo do tipo Pergunta. O mesmo é feito
no método retornaResposta().
Não há muito segredo: quando precisamos acessar
nosso banco de dados, tudo que precisamos fazer é interagir o pacote view com o pacote impl. Isto é feito chamando a classe BancoDaoImpl, que na tela QuizzGeografia.java
está armazenada na memória através da variável mDb.
E isso é tudo. O exemplo demonstrado neste post foi bem simples e básico
(se comparado aos exemplos que serão mostrados nos próximos post do Blog). Mas o
conceito básico não muda: quando formos nosso banco de dados, tudo que
precisamos fazer é interagir um pacote view
com um pacote impl.
O que mudará nos posts subsequentes deste
Blog é a maneira como os dados são obtidos do banco de dados, o que pode
ser realizado de maneira muito menos trabalhosa.
-----
E aqui terminamos mais um tópico do blog.
Caso alguém tenha uma dúvida, crítica ou sugestão, sinta-se à vontade.