domingo, 30 de agosto de 2009

Utilizando Banco de Dados SQLite no Android

Olá povo,

Entre as muitas coisas legais que o Android trouxe, o suporte nativo ao banco de dados SQLite foi uma das mais importantes, comercialmente falando. Com ele podemos utilizar os bons e famosos comandos SQL (SELECT, INSERT, UPDATE, DELETE, etc) no mundo mobile. Quem programou em J2ME sabe o quão complicado é implementar um repositório "digno" que suporte uma boa quantidade de registros nessa plataforma (não me falem de RMS... :)

Nesse POST, vou colocar trechos de código que utilizam esses conceitos e tentarei explicá-los. A parte da interface gráfica, vou explicar apenas o que for relevante para o uso com banco de dados.

Para criarmos um banco de dados SQLite, podemos:
  • utilizar a ferramenta SQLite Expert Personal e colocar o arquivo do banco de dados na pasta /data/data/pacote_da_minha_aplicacao/databases;
  • ou via código, utilizando uma classe "helper" do SQLite.
Utilizaremos a segunda abordagem, pois na vida real não queremos colocar esse arquivo manualmente no aparelho. Pois, caso seja necessária uma atualização, teremos que atualizar N aparelhos.

Mãos à obra! Vamos criar uma classe que herda de android.database.sqlite.SQLiteOpenHelper. Ela será responsável por criar o banco quando a aplicação for instalada (método onCreate) e atualizá-lo para novas versões da aplicação (método onUpgrade).

public class SQLiteHelper extends SQLiteOpenHelper {
  private String scriptCreate;
  private String scriptDelete;

  public SQLiteHelper(Context ctx, String nomeBd, 
      int versaoBanco, String scriptCreate, 
      String scriptDelete) {

      super(ctx, nomeBd, null, versaoBanco);
      this.scriptCreate = scriptCreate;
      this.scriptDelete = scriptDelete;
  }

  public void onCreate(SQLiteDatabase db) {
      db.execSQL(scriptCreate);
  }

  public void onUpgrade(SQLiteDatabase db, 
      int oldVersion, int newVersion) {

      db.execSQL(scriptDelete);
      onCreate(db);
  }
}


Agora vamos criar a classe que realizará as operações de abrir, selecionar, inserir, atualizar e excluir registos no banco de dados:

public class RepositorioCarro {
  private SQLiteDatabase db;
  private SQLiteHelper dbHelper;
  private static final String SCRIPT_DB_DELETE = 
    "DROP TABLE IF EXISTS carros";
  private static final String SCRIPT_DB_CREATE =
    "create table carros (_id integer primary "+
    "key autoincrement, nome text not null, "+
    "placa text not null, ano text not null);";

  public RepositorioCarro (Context ctx){
    dbHelper = new SQLiteHelper(ctx, "curso", 1, 
      SCRIPT_DB_CREATE, SCRIPT_DB_DELETE);
  }

  private long inserir(Carro c){
    ContentValues cv = new ContentValues();
    cv.put("nome", c.getNome())
    cv.put("placa", c.getPlaca());
    cv.put("ano", c.getAno());
    db = dbHelper.getWritableDatabase();
    long id = db.insert("carros", null, cv);
    db.close();
    return id;
  }

  private long atualizar(Carro c){
    ContentValues cv = new ContentValues();
    cv.put("nome", c.getNome());
    cv.put("placa", c.getPlaca());
    cv.put("ano", c.getAno());

    db = dbHelper.getWritableDatabase();
    long rows = db.update("carros", cv, "_id = ?", 
       new String[]{ String.valueOf(c.getId())});
    db.close();
    return rows; // qtde. de linhas afetadas 
  } 

  public int excluir(int id){
    db = dbHelper.getWritableDatabase();
    int rows = db.delete("carros", "_id = ?", 
      new String[]{ String.valueOf(id) });
    return rows; // qtde. de linhas afetadas
  }

  public List<Carro> buscarCarroPorNome(String nome){
    List<Carro> lista = new ArrayList<Carro>();

    String[] columns = new String[]{
       "_id", "nome", "placa", "ano"};
    String[] args = new String[]{nome+"%"};

    db = dbHelper.getWritableDatabase();
    Cursor c = db.query("carros", columns, 
       "nome like ?", args, null, null, "nome");

    c.moveToFirst();
    while(!c.isAfterLast()){
      Carro carro = fillCarro(c);
      lista.add(carro);
      c.moveToNext();
    }
    c.close();
    db.close();
    return lista;
  }

  private Carro fillCarro(Cursor c) {
    Carro carro = new Carro();
    carro.setId((int)c.getLong(0));
    carro.setNome(c.getString(1));
    carro.setPlaca(c.getString(2));
    carro.setAno(c.getString(3));
    return carro;
  }
}


Como vocês podem ver, estou usando uma classe Carro também. Não vou listá-la aqui por se tratar de um POJO com os campos: id, nome, placa e ano do veículo.

Na classe acima, o construtor recebe uma referência do contexto da aplicação. Ela é repassada para nossa classe SQLHelper, que por sua vez, se encarregará de criar o nosso banco de dados se ele não existir. A classe android.database.sqlite.SQLiteOpenHelper (superclasse do nosso helper) tem o método getWritableDatabase() que retorna uma instância da classe android.database.sqlite.SQLiteDatabase, que será nosso canal de comunicação com o banco de dados.

As operações de inserir e atualizar utilizam: uma string que representa o nome da tabela no banco de dados; e um objeto da classe ContentValues, que nada mais é do que um conjunto par/valor com os parâmetros do comando SQL. Mas notem que o método update recebe ainda uma String que representa a cláusula WHERE do comando UPDATE (do SQL), bem como os parâmetros da mesma, que estão mapeados via caracter '?'.
"Por tabela", o método excluir vocês já devem ter entendido :)

O último método é o buscarCarroPorNome(String). Toda consulta SQL, retorna um objeto do tipo Cursor. Ele é bem parecido com o ResultSet do JDBC. Os parâmetros do método query são, respectivamente: String nome da tabela, String[] colunasDaTabela, String clausulaWhere, String[] paramsWhere, String groupBy, String having, String orderBy.

Para exibir os dados, precisaremos criar um Adapter, que converterá um Carro em uma linha de uma lista. Crie o arquivo de layout list_item.xml e coloque-o na pasta res/layout:

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

<LinearLayout android:layout_height="wrap_content" 
xmlns:android="http://schemas.android.com/apk/res/android" 
android:layout_width="fill_parent" 
android:id="@+id/carroRow">

<TextView android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:id="@+id/txtNome" 
android:layout_weight="1" 
android:textSize="20dp"/>

<TextView android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:id="@+id/txtAno" 
android:layout_marginRight="10px"/>

<TextView android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:id="@+id/txtPlaca"  
android:layout_marginRight="10px"/>

</LinearLayout>


E agora, vamos criar nosso adapter.

public class CarroListAdapter extends BaseAdapter {
  private Context context;
  private List<Carro> lista;

  public CarroListAdapter(Context ctx, 
    List<Carro> carros) {

    context = ctx;
    lista = carros;
  }

  public int getCount() {
    return lista.size(); 
  }

  public Object getItem(int arg0) {
    return lista.get(arg0);
  }

  public long getItemId(int arg0) {
    return lista.get(arg0).getId();
  }

  public View getView(int pos, View convertView, 
    ViewGroup parent) {

    Carro c = lista.get(pos);
    LayoutInflater inflater = 
      (LayoutInflater)context.getSystemService(
        Context.LAYOUT_INFLATER_SERVICE);

    View view = inflater.inflate(
      R.layout.list_item, null);

    ((TextView)view.findViewById(R.id.txtNome)).
      setText(c.getNome());

    ((TextView)view.findViewById(R.id.txtAno)).
      setText(c.getAno());

    ((TextView)view.findViewById(R.id.txtPlaca)).
      setText(c.getPlaca());

    return view;
  }
}


Crie uma classe que herde de ListActivity e no onCreate, digite o seguinte código:
RepositorioCarro carrosDB = new RepositorCarro(this);
List<Carro>lista = carrosDB.buscarCarroPorNome("");
setAdapter(new CarroListAdapter(this, lista));

Para utilizar os demais métodos é facinho, não é? Basta criar objetos do tipo Carro, no qual os valores serão preenchidos por widgets (componentes de UI) e passá-los como parâmetros para os métodos do RepositorioCarro.

Espero que tenham entendido e gostado :)

Editado em 03/11/2011
Sempre que fizermos uma operação no banco de dados é importante que encerremos a conexão com ele. Tinha faltado isso nesse post, e agora lembrei de corrirgir.

4br4ç05
nglauber

35 comentários:

Wagner disse...

Muito bom Nelson,

Primeiro de tudo, muito obrigado!

Estou desenvolvendo uma app para um trabalho do curso, como tinha apenas dois dias (agora um dia) para desenvolver resolvi utilizar a xtreamxml, pensando que seria mais fácil para meu projeto.

Mas com esse seu post descobri como é fácil utilizar a SQLite no Android.

Muito obrigado pela contribuição :)
abraco!

Felipe Alexandre Rodrigues disse...

Parabéns! Ficou muito bom.
Eu estava procurando por um post falando sobre banco de dados no android !
Valeu mesmo

thiago silva disse...

Glauber, pelo que observei caso eu tenha um banco de dados físico e queira coloca-lo na aplicação teria que ser feito sempre de forma manual?Tanto para o simulador quanto celular? Tenho todo o script do banco de dados, porém sempre tem o detalhe de consertar as Strings para serem aceitas.Existe alguma maneira prática para fazer este procedimento?
Fico no aguardo...

Abraços!

Nelson Glauber de Vasconcelos Leal disse...

Oi thiago,

Na verdade você pode adotar algumas abordagens quando vc tem um banco de dados inicial para a aplicação.
1) Utilizar um arquivo de script que será executado no seu SQLiteOpenHelper.
2) Utilizar um arquivo XML para alimentar o banco (recém criado)
3) Colocar o arquivo DB dentro do APK e copiá-lo (via API java) para o diretório /data/data/pacote.da.app/databases.
4) Acessar um WebService...

Quem manda é o freguês :)

4br4ç05

Anônimo disse...

Procurei na internet algum exemplo da opção 3(Colocar o arquivo DB dentro do APK e copiá-lo (via API java) para o diretório /data/data/pacote.da.app/databases.),mas não obtive sucesso. :/

Nelson Glauber de Vasconcelos Leal disse...

Oi Anônimo,

Como você foi a milhonésima pessoa que me perguntou isso :) eu resolvi fazer um POST só com isso.

http://nglauber.blogspot.com/2011/06/copiando-um-banco-de-dados-sqlite-no.html

Espero que te ajude.
4br4ç05,
nglauber

carla disse...

OI NELSON. GOSTARIA DE SABER SE VC PODE ME TIRAR UMA DÚVIDA. EU CRIEI UMA TABELA, SÓ QUE QUERO ADICIONAR NOVOS CAMPOS NNESSA TABELA, MAS DÁ ERRO TODA VEZ QUE RODO A APLICAÇÃO INSERINDO NOVOS CAMPOS. TENTEI O ALTER TABLE, TBME NÃO FUNCIONOU.TEM ALGUMA SOLUÇÃO???
GRATA

CARLA

Nelson Glauber de Vasconcelos Leal disse...

Oi Carla,

Me manda um e-mail explicando como você está querendo fazer essa atualização.

4br4ç05,
nglauber

lindemberg disse...

Nelson, muito bem explicado o tutorial mas eu ainda fiquei com algumas dúvidas, ond e com vc declara esse setContent no final do código? se possivel for, me envia o seu código completo pra mim dar uma sacada. Pode ser? berg_wylhame@hotmail.com

Jéh disse...

Ei, estou com algumas dúvidas, por exemplo como faço para saber o id de um carro criado, por exemplo?

jessicapiresf@gmail.com

Nelson Glauber de Vasconcelos Leal disse...

Oi Jéh,

O método insert da classe SQliteDatabase retorna um long que é o ID do registro que foi incluído.

http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html

4br4ç05,
nglauber

Anônimo disse...

olá
bem, testei o código numa aplicação que estou desenvolvendo, funcionou nas duas primeiras vezes que executei, depois surgiu o erro no listar. Sendo que esse erro afeta a activity que solta a lista na tela para que eu tenha acesso pelo menu as opções de inserir e buscar, assim nao consigo inserir e nem buscar.
esse erro pode ser um problema no meu eclipse ou sdk?? pq de uma hora pra outra ele nao funcionou mais e o código tem ares de estar certo :/

Nelson Glauber de Vasconcelos Leal disse...

Oi Anônimo,

Tenta apagar a aplicação e instalar de novo. Normalmente isso acontece quando você altera a estrutura da tabela após ter executado a primeira vez (e consequentemente o banco já está criado) e tenta acessar esse campo novo, que não existe.

Tenta postar o stacktrace aqui.

4br4ç05,
nglauber

Alysson Bispo disse...

Glauber, no emulador funcionou beleza, mas no aparelho não deu certo não....tem algum misterio ? alguma permissão para adicionar ?

André Victor disse...

Muito bom o tutorial, Glauber.
Me ajudou bastante!

Alcool disse...

Nelson como eu faria pra a seguinte situacao:

Alimentar minhas tabelas atravez de arquivo XML.

Por ex: Tenho minha app Forca de vendas com as tabelas cliente, produto, etc... apartir de uma XML como alimento cada tabela ??

Nelson Glauber de Vasconcelos Leal disse...

Oi Alcool,

Você pode usar esse post abaixo para ver como ler um arquivo XML
http://nglauber.blogspot.com.br/2011/11/lendo-rss-no-android.html

E para cada linha lida, você adiciona no banco...

4br4ç05,
nglauber

Leerlauf disse...

Glauber! Qual a melhor maneira(visando o desempenho) de inicializar um banco. Em meu aplicativo tenho um banco fixo, não tenho operações. Somente carrego as informações em layouts diferentes.

Nelson Glauber de Vasconcelos Leal disse...

Oi Leerlauf,

Dá uma olhada nesse post aqui. Acho que é o que você quer...
http://nglauber.blogspot.com.br/2011/06/copiando-um-banco-de-dados-sqlite-no.html

4br4ç05,
nglauber

Emir Calife disse...

Existe o fonte em algum lugar para download?

Nelson Glauber de Vasconcelos Leal disse...

Oi Emir,

É só copiar e colar :p

4br4ç05,
nglauber

Anônimo disse...

Tente uma ferramenta gratuita - Valentina Studio. Produto surpreendente! IMO é o melhor SQLite Manager, para todas as plataformas. http://www.valentina-db.com/en/valentina-studio-overview

Nelson Glauber de Vasconcelos Leal disse...

oi Anônimo,

Boa dica!

4br4ç05,
nglauber

Matheus Weber disse...

Boa noite!

Curtir muito o tutorial, só que eu to com uma duvida meio urgente assim.

Eu adaptei o seu codigo para uma lista de produtos, a principio ele funcionou até a parte de inserir, mas não consigo mostrar a lista de jeito nenhum, sempre que eu tento ele encerra o aplicativo, acredito que estou fazendo errado, tentei colocar numa listView desse jeito:

list = (ListView)findViewById(R.id.listView1);
list.setAdapter(new produtoListAdapter(this, lista));

Mas ele encerra o aplicativo, percebi que o erro se encontra ae, pois quando tiro, ele mostra.

A listview no xml está assim:

< Lis tView
android:id="@+id/listView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignTop="@+id/view1" >
< / Li stView>

Nelson Glauber de Vasconcelos Leal disse...

oi Matheus,

Difícil de dizer assim... Mas se você estiver usando uma ListActivity sua ListView tem que ter o id @android:id/list. Caso não esteja, dá uma olhada se o problema não é no adapter...
No LogCat, na maioria dos casos, mostra exatamente onde é o erro.

4br4ç05,
nglauber

Anônimo disse...

Glauber, é possivel listar banco de dados SQLite de outros app e exporta-los?

Nelson Glauber disse...

Oi anônimo,

A princípio não, pois isso envolve a segurança do aplicativo. Cada app tem um usuário, e os arquivos que são criados por esse usuário não são acessíveis a outros.
Entretanto, se seu aparelho for rooteado creio que seja possível. Uma outra possibilidade é se você tiver dois aplicativos e que compartilhar o banco de dados entre eles. Nesse caso é melhor utilizar o Content Provider.

4br4ç05,
nglauber

Unknown disse...

Carla.... Use o onUpgrade, e faça as devidas alterações com o alter table... E mude a versao do seu banco... Ai, a hora que for rodar, o android vai comprar as versoes, e vai direto pro onUpgrade..

Israel Tavares disse...

Glauber ,tudo bem ? Sou muito fã dos seus livros 1ª e 2ª edicões. E queria dizer que seu livro da 2ª edição tem me ajudado muito! Queria tirar uma pequena dúvida se você puder me ajudar,claro! No seu livro Cap.10, sobre acesso á Web-parte 2, tem um momento que vc pede pra gente instalar o Xampp. Só que eu tenho usado muito o Wamp, mas não sei distinguir onde colocar a parte que vc pediu pra colocar no htdocs do xampp, quando na verdade, quero fazer funcioná-lo no Wamp. Como seria o procedimento? Pq ja tentei de varias formas, mas não consegui fazer aparecer o banco de dados pelo Wamp. Só funciona pelo aplicativo Xampp! Me ajuda,plis!!!

Nelson Glauber disse...

Oi Israel,

Que bom que gostou dos livros ;)
Infelizmente nunca usei o wamp, mas a ideia é que os scripts PHP fique em uma subpasta do htdocs do apache. Ex.: htdocs/hotel/webservice.php.
Então você conseguiria acessar esse serviço utilizando locahost/hotel/webservice.php. Qualquer dúvida, posta lá no grupo de discussão do livro ;)

4br4ç05,
nglauber

Israel Tavares disse...

Glauber,no capitulo 10 do seu livro 2ª edição. Na página 396, em que se faz o teste do servidor xampp, que coloca o ip da conexao local ou virtual box, eu gostaria de testar num site que criei só p teste em desenolvivmento PHP. Me cadastrei na hostgator há uns meses, e eu gostaria de conectar remotamente do servidor cpanel, da mesma forma com o xampp. Como eu faço?

Nelson Glauber disse...

Oi Israel,

Se o seu webservice está hospedado em um servidor, basta colocar o endereço do seu servidor...
http://www.seusite.com/webservice.php

4br4ç05,
Glauber

Israel Tavares disse...

Ah sim!!! Mt obrigado! Deu certo, Glauber!!! Deus o abençoe!!!!

Israel Tavares disse...

Glauber,bom dia. Uma duvida pequena e que não encontro pela internet rsrs...
To criando um aplicativo só p/ testes, sobre envio de sms, então queria no EditText tivesse varias linhas,então procurei no Cap.4 sobre EditText, aí só faltou como ficaria caso eu quisesse tratar na XML sobre android:ime="actionDone" juntamente com o codigo android:inputType="textMultiLine".
Na hora de clicar o botao "done" do teclado nao aparece, e só aparece quando
removo o android:inputType="textMultiLine". Qual seria o procedimento?

Nelson Glauber disse...

Oi Israel,

A propriedade imeAction ditará como será a ação do botão de teclado. Se você quer o texto do EditText multi-line, o botão será o enter. Então, creio que não seja possível ter o multi-line + o botão de ação. Se você olhar os aplicativos mais famosos (whatsapp, twitter, facebook, ...) eles não possuem esse recurso.

4br4ç05,
nglauber