quarta-feira, 30 de outubro de 2013

Banco de dados - Parte 1

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).
  • 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ávelTODA 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;
  • ConstanteToda 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áticosUma 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.

2 comentários:

  1. Dê uma olhada em uma ferramenta gratuita – Valentina Studio. Produto surpreendente! IMO este é o melhor gerente para o SQLite para todas as plataformas. http://www.valentina-db.com/en/valentina-studio-overview

    ResponderExcluir
  2. Ta faltando implementar os metodos

    @Override
    public Cursor pesquisaResposta(Integer idPergunta) {
    return null;
    }

    @Override
    public void populaTabelaPergunta() {

    }

    @Override
    public Cursor listarResposta() {
    return null;
    }

    ResponderExcluir