segunda-feira, 18 de maio de 2015

Trabalhando com banco de dados SQLite no Android

Uma das coisas que me deixaram de queixo caído quando comecei a aprender a desenvolver para Android foi saber que todo celular com esse sistema operacional vem de fábrica com um servidor de banco de dados relacional completo embutido, gratuito e pronto para o uso. Quem trabalha com informática há muitos anos e já teve experiência com grandes servidores de banco de dados Oracle ou SQL Server compartilhará da mesma opinião.

O Android vem com um banco de dados conhecido como "SQLite". Ele está embutido em todo dispositivo Android. É um banco de dados "open source" e suporta as ferramentas relacionais tradicionais, queries em SQL e transações. Requer pouquíssima memória para executar (cerca de 250KB). 

Para que o desenvolvedor utilize SQLite não é necessário nenhum procedimento de instalação ou mesmo privilégios de administrador de banco de dados. O banco apenas está lá, aguardando para ser utilizado.

Só é preciso definir algumas instruções SQL para criar e atualizar a base de dados. À partir daí a plataforma Android administra o banco automaticamente.

Acessar o banco de dados SQLite envolve a utilização do sistema de arquivos, o que pode trazer algum overhead. Portanto, acessos à base de dados em aplicativos reais devem ser feitos de forma assíncrona.

Quando uma aplicação cria um banco de dados, por padrão ele será armazenado no seguinte diretório (da memória interna do aparelho):

data/data/[nomeDoApp]/databases/[nomeDoArquivo.db]

A criação do banco de dados em aplicativos Android é feita utilizando-se a classe "SQLiteOpenHelper".

Vamos então, começar o trabalho:

Primeiro vamos criar um projeto chamado "projetoBancoDeDados". Então, na pasta "src" vamos criar uma classe chamada "MySQLiteHelper", que será responsável por criar a base (caso ela ainda não exista) ou atualizá-la (caso tenhamos modificado o valor da versão do banco de dados), com o seguinte conteúdo:

package com.example.projetobancodedados;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class MySQLiteHelper extends SQLiteOpenHelper
{

   // Define o nome da base de dados e a versão.
   private static final String DATABASE_NAME="banco.db";
   private static final int DATABASE_VERSION=1;

   // Script de criação do banco de dados.
   private static final String DATABASE_CREATE = " create table if not exists funcionario (" +
                                                                        " id integer primary key autoincrement," +
                                                                        " nome varchar(100), " +
                                                                        " cargo varchar(50), " +
                                                                        " salario real " +
                                                                        " );";


   public MySQLiteHelper(Context context)
   {
      super(context, DATABASE_NAME, null, DATABASE_VERSION);
   }

   @Override
   public void onCreate(SQLiteDatabase database)
   {
      // Cria o banco de dados, executando o SQL.
      database.execSQL(DATABASE_CREATE);
   }

   @Override
   public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
   {
      // Elimina e recria a base, caso o valor da versão tenha sido alterado.
      db.execSQL("DROP TABLE IF EXISTS funcionario ");
      onCreate(db);
   }

}


O próximo passo é criar uma classe "FuncionarioDTO", que definirá a estrutura que utilizaremos para armazenar um funcionário no banco de dados e também recuperá-lo:


package com.example.projetobancodedados;

public class FuncionarioDTO
{
   private int id;
   private String nome;
   private String cargo;
   private Double salario;

   public int getId()
   {
      return id;
   }
   public void setId(int id)
   {
      this.id=id;
   }
   public String getNome()
   {
      return nome;
   }
   public void setNome(String nome)
   {
      this.nome=nome;
   }
   public String getCargo()
   {
      return cargo;
   }
   public void setCargo(String cargo)
   {
      this.cargo=cargo;
   }
   public Double getSalario()
   {
      return salario;
   }
   public void setSalario(Double salario)
   {
      this.salario=salario;
   }   
}


Agora vamos criar a classe mais importante, que será o nosso ponto de acesso ao banco de dados (DAO). Vamos nomear essa classe "FuncionarioDAO". Definiremos aqui os métodos "save", "delete" e "getAll", que serão responsáveis em gravar e apagar um funcionário, e obter todos os registros de funcionário:



package com.example.projetobancodedados;


import java.util.ArrayList;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;

public class FuncionarioDAO
{

   private SQLiteDatabase database;
   private MySQLiteHelper dbHelper;

   public FuncionarioDAO(Context context)
   {
      dbHelper=new MySQLiteHelper(context);
   }


   // Abre o banco de dados.
   public void open() throws SQLException
   {
      database=dbHelper.getWritableDatabase();
   }

   
   // Fecha o banco de dados.
   public void close()
   {
      dbHelper.close();
   }

   
   // Insere o funcionário, caso ainda não exista. Ou atualiza.
   public boolean save(FuncionarioDTO funcionario)
   {
      ContentValues values=new ContentValues();
      values.put("id", funcionario.getId());
      values.put("nome", funcionario.getNome());
      values.put("cargo", funcionario.getCargo());
      values.put("salario", funcionario.getSalario());

      Cursor cursor=database.rawQuery("SELECT 1 from funcionario where id = " + funcionario.getId(), null);

      if (cursor.getCount() == 0)
      {
         database.insert("funcionario", null, values);
      }
      else
      {
         database.update("funcionario", values, " id = ? ", new String[] {  String.valueOf(funcionario.getId()) });
      }

      return true;

   }

   
   // Apaga um registro de funcionário.
   public void delete(FuncionarioDTO funcionario)
   {
      int id=funcionario.getId();
      database.delete("funcionario", " id = " + id, null);
   }

   
   // Retorna uma lista com todos os funcionários.
   public ArrayList<FuncionarioDTO> getAll()
   {
      ArrayList<FuncionarioDTO> funcionarios=new ArrayList<FuncionarioDTO>();

      Cursor cursor=database.rawQuery("SELECT * from funcionario;", null);

      cursor.moveToFirst();
      while (!cursor.isAfterLast())
      {
         FuncionarioDTO funcionario=cursorToFuncionario(cursor);
         funcionarios.add(funcionario);
         cursor.moveToNext();
      }

      // Fecha o cursor.
      cursor.close();

      // Retorna lista de funcionários.
      return funcionarios;

   }

   // Converte de cursor para FuncionarioDTO.
   private FuncionarioDTO cursorToFuncionario(Cursor cursor)
   {
      FuncionarioDTO funcionario=new FuncionarioDTO();
      funcionario.setId(cursor.getInt(0));
      funcionario.setNome(cursor.getString(1));
      funcionario.setCargo(cursor.getString(2));
      funcionario.setSalario(cursor.getDouble(3));

      return funcionario;
   }

}


Por último, vamos alterar a classe "MainActivity" para que fique como o código descrito abaixo. O que estamos fazendo aqui é incluir três funcionários (JOSÉ, MARIA E HENRIQUE) e, logo em seguida, apagar o registro de um deles (MARIA), para exemplificar as operações. Você poderá modificar o código para experimentar livremente a inclusão de outros funcionários e a alteração dos dados dos já existentes.

Também colocamos a funcionalidade de listar todos os registros existentes. Sendo que, neste exemplo, a saída é realizada no LogCat (devendo existir uma tag "debug" para visualização no Eclipse).

Observe com especial atenção os métodos onResume() e onPause(). É muito importante fechar o banco e reabri-lo nos momentos corretos.


package com.example.projetobancodedados;

import java.util.ArrayList;
import android.os.Bundle;
import android.app.Activity;
import android.util.Log;

public class MainActivity extends Activity
{

   // Define o ponto de acesso ao banco (DAO) e o objeto da classe descritiva de funcionário.
   private FuncionarioDAO funcionarioDAO;
   private FuncionarioDTO funcionario;
   
   @Override
   protected void onCreate(Bundle savedInstanceState)
   {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      // Abre o banco de dados de funcionário.
      funcionarioDAO=new FuncionarioDAO(this);
      funcionarioDAO.open();

      // Instancia em memória um novo funcionário e preenche seus dados.
      funcionario = new FuncionarioDTO();
      funcionario.setId(1);
      funcionario.setNome("JOSÉ SILVA");
      funcionario.setCargo("Auxiliar administrativo");
      funcionario.setSalario(985.63);
      // Grava no banco (insere ou atualiza) o funcionário.
      funcionarioDAO.save(funcionario);

      
      // Instancia outro funcionário.
      funcionario = new FuncionarioDTO();
      funcionario.setId(2);
      funcionario.setNome("MARIA JOSÉ");
      funcionario.setCargo("Cozinheira");
      funcionario.setSalario(627.35);    
      // Grava no banco (insere ou atualiza) o funcionário.
      funcionarioDAO.save(funcionario);


      // Instancia outro funcionário.
      funcionario = new FuncionarioDTO();
      funcionario.setId(3);
      funcionario.setNome("HENRIQUE ARIAS");
      funcionario.setCargo("Motorista");
      funcionario.setSalario(1239.56);
      // Grava no banco (insere ou atualiza) o funcionário.
      funcionarioDAO.save(funcionario);
      
      
      // Apaga um funcionário.
      funcionario.setId(2);
      funcionarioDAO.delete(funcionario);
      

      // Obtem lista de todos os funcionários da base.
      ArrayList<FuncionarioDTO> listaDeFuncionarios = new ArrayList<FuncionarioDTO>();
      listaDeFuncionarios=funcionarioDAO.getAll();
      
      // Mostra no LogCat todos os funcionários da base.
      for(FuncionarioDTO f: listaDeFuncionarios)
      {
         
         Log.i("debug", f.getId() + "");
         Log.i("debug", f.getNome());
         Log.i("debug", f.getCargo());
         Log.i("debug", f.getSalario() + "");
         
      }
      
   }

   @Override
   protected void onResume()
   {
      // Reabre o banco.
      funcionarioDAO.open();
      super.onResume();
   }

   @Override
   protected void onPause()
   {
      // Fecha o banco.
      funcionarioDAO.close();
      super.onPause();
   }

}


Depois que executarmos o código pela primeira vez, poderemos visualizar as tabelas do banco de dados e seu conteúdo através do próprio Eclipse (disponível apenas para quem baixou nosso ambiente de desenvolvimento), utilizando o "Questoid SQLite browser", que é um plugin que já incluímos no nosso ambiente.

Para isto, devemos utilizar a aba "DDMS" no Eclipse, conforme a imagem:




E então devemos clicar no nome do dispositivo e navegar até o arquivo de banco de dados criado pelo nosso aplicativo na pasta:

"data/data/com.example.projetobancodedados/banco.db"

Clicando no ícone azul (com desenho de um banco de dados) no canto superior direito da tela, teremos acesso ao "Questoid SQLite browser" na parte de baixo da tela. Ele nos permite ver tanto a estrutura do banco de dados quanto os valores, conforme ilustram as imagens abaixo:






Para quem preferir baixar o projeto completo, ei-lo aqui para download.

Com isto, demonstramos com sucesso, de forma introdutória, como utilizar banco de dados local no Android. O artigo, obviamente, não esgota o assunto, mas fornece ao leitor os subsídios necessários para ter uma ideia a respeito do uso do banco neste ambiente.

Isso abre novas perspectivas, permitindo a criação de apps ainda mais úteis. Também significa novas possibilidades de otimização (utilizando o banco local como cache) e, inclusive, o desenvolvimento de aplicativos offline (atualizando seus dados somente na presença de sinal de Internet).

Espero que tenha sido proveitoso. Um forte abraço e até a próxima!

Nenhum comentário:

Postar um comentário