terça-feira, 31 de janeiro de 2012

Persistência de dados com SQLite

É notável que muitas aplicações precisam de alguma forma de persistência de dados, assim você ainda terá informações quando seu aplicativo for finalizado ou o aparelho for desligado. Isso pode ser feito de diversas maneiras: 

  • Armazenamento interno: armazena arquivos na memória do aparelho. Esse arquivo está disponível apenas para sua aplicação, nem manualmente o usuário consegue acesso a ele. Esses arquivos serão deletados quando o aplicativo for desinstalado;
  • Armazenamento externo: armazena arquivos na memória externa do aparelho, que normalmente é o cartão (micro)SD. Esse arquivo pode ser acessado por qualquer aplicativo e estará acessível para o usuário também;
  • Armazenamento online: utilizar um servidor próprio para armazenar informações. Depende diretamente de uma conexão com internet, mas não depende no dispositivo (pode ser mantida se o dispositivo for inutilizado, ou o aplicativo desinstalado;
  • Shared Preferences: sistema de armazenamento próprio do Android, que permite armazenar dados na forma de HashMap (chave -> valor). Pode ser facilmente vinculada a uma PreferenceActivity para criar uma tela de configurações/preferencias;
  • Banco de dados SQLite: o Android fornce ferramentas para fácil acesso ao SQLite. E é o que iremos tratar aqui.
Mais informações sobre armazenamento de dados aqui.

Precisamos de duas classes básicas para usar o SQLite: SQLiteOpenHelper e SQLiteDatabase. A primeira vai lidar com a criação do banco de dados, upgrade, e acesso ao SQLiteDatabase que é o banco de dados em si.

Precisamos implementar uma subclasse da primeira para configurar nosso banco de dados e implementar os métodos onCreate(SQLiteDatabase db)  e onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion). onCreate é executado somente uma vez, quando o banco de dados é acessado pela primeira vez. Aqui deve-se criar as tabelas e popular o banco com o que precisar. onUpgrade é chamado toda vez que a versão do banco aumenta, versão esta que não está necessariamente associada a versão do aplicativo e é definida pelo desenvolvedor.

Para usar o banco de dados deve-se instanciar a sub-classe de SQLiteOpenHelper criada por você fornecendo o nome do banco de dados (geralmente há apenas um banco de dados para cada aplicação), e a versão atual do banco. Depois é só usar o método dessa instancia getWritableDatabase() que irá retornar uma SQLiteDatabase pronta para uso. Agora basta usar os métodos query, insert, update, delete, execSQL, entre outros, para manipular os dados como queira. Não se esqueça de usar o metodo close() do SQLiteOpenHelper para fechar os bancos depois do uso.

Agora irei apresentar a forma como uso, que visa fornecer uma interface de fácil acesso e mais resistente a erros. A ideia é criar uma classe que lidará com tudo por você, desde a criação até manipulação dos dados:

public class DbAdapter {
private static final String DATABASE_NAME = "meuappdatabase";
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_TABLE_CARROS = "carros";
public static final String KEY_ID = "_id";
public static final String KEY_NOME = "nome";
public static final String KEY_ANO = "ano";
private DatabaseHelper mDbHelper;
private SQLiteDatabase mDb;
private static final String[] DATABASE_CREATE = { "create table " + DATABASE_TABLE_CARROS + " (" 
+ KEY_ID + " integer primary key autoincrement, "
+ KEY_NOME + " varchar(255) not null, "
+ KEY_ANO + " integer not null);"
};
 
private final Context mCtx; 
public DbAdapter(Context ctx){
this.mCtx = ctx;
}
public DbAdapter open() throws SQLException{
mDbHelper = new DatabaseHelper(mCtx);
mDb = mDbHelper.getWritableDatabase();
return this;
}
public void close(){
mDbHelper.close();
}
public long createCarro(String nome, int ano){
ContentValues initialValues = new ContentValues();
initialValues.put(KEY_NOME, nome);
initialValues.put(KEY_ANO, ano);
long id = mDb.insert(DATABASE_TABLE_CARROS, null, initialValues);
return id;
}
public boolean deleteCarro(long id){
int qt = mDb.delete(DATABASE_TABLE_CARROS, KEY_ID + "=" + id, null);
return qt > 0;
}
public Cursor getAllCarros(){
Cursor mCursor = mDb.query(DATABASE_TABLE_CARROS, null, null, null, null, null, null);
if(mCursor != null){
mCursor.moveToFirst();
}
return mCursor;
}
public Cursor getCarro(long id) throws SQLException {
Cursor mCursor = mDb.query(true, DATABASE_TABLE_CARROS, null, KEY_ID + "=" + id, null, null, null, null, null);
if(mCursor != null){
mCursor.moveToFirst();
}
return mCursor;
}
public boolean updateCarro(long id, String nome, int ano){
ContentValues args = new ContentValues();
args.put(KEY_NOME, nome);
args.put(KEY_ANO, ano);
return mDb.update(DATABASE_TABLE_CARROS, args, KEY_ID + "=" + id, null) > 0;
}
class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db){
for(int i = 0; i < DATABASE_CREATE.length; i++)
db.execSQL(DATABASE_CREATE[i]);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}

Pronto. Agora você tem uma interface de fácil uso. Para utilizar basta instanciar o DbAdapter, abri-lo com open() e fazer as operações que quiser. No final basta fecha-lo com close(). Por exemplo:

DbAdapter mDbAdapter = new DbAdapter(MinhaActivity.this);
mDbAdapter.open();
long carroId = mDbAdapter.createCarro("Ferrari", 2000);
mDbAdapter.updateCarro(carroId, "Mercedes", 2005);
mDbAdapter.deleteCarro(carroId);
mDbAdapter.close();

No caso de comandos query, aqueles que retornam dados (SELECT), o tipo de dado retornado é o Cursor. Basicamente ele é uma lista de HashMap. Sempre temos que executar moveToFirst() pois quando ele termina de ser criado a sua posição é depois do ultimo registro. A partir daí podemos acessar dados a partir dos métodos getInt, getString, getLong, etc... usando como parâmetro o índice da coluna na tabela, que pode - e deve - ser retornado pelo método getColumnIndex e/ou getColumnIndexOrThrow. Por exemplo:

Cursor carros = mDbAdapter.getAllCarros();
String nomeCarro1 = carros.getString(carros.getColumnIndex(DbAdapter.KEY_NOME));
carros.moveToNext();
String nomeCarro2 = carros.getString(carros.getColumnIndex(DbAdapter.KEY_NOME));
int anoCarro2 = carros.getInt(carros.getColumnIndex(DbAdapter.KEY_ANO));

E podemos navegar pelos registros através dos comandos move, moveToFirst, moveToLast, moveToNext, moveToPrevious e moveToPosition.

Se você deseja um pouco mais de abstração e não quer ficar lidando com Cursor toda hora você pode criar uma classe para que representa o objeto guardado na tabela. Por exemplo:

public class Carro {
public long Id;
public int ano;
public String nome;
public Carro(long id, int ano, String nome) {
this.Id = id;
this.ano = ano;
this.nome = nome;
}
}

E mudar os métodos getCarro e getAllCarros para retornar instancias de Carro:

public Carro[] getAllCarros(){
Cursor mCursor = mDb.query(DATABASE_TABLE_CARROS, null, null, null, null, null, null);
if(mCursor != null){
mCursor.moveToFirst();
Carro[] carros = new Carro[mCursor.getCount()];
for(int i = 0; i < carros.length; i++,mCursor.moveToNext())
carros[i] = new Carro(mCursor.getLong(mCursor.getColumnIndex(KEY_ID)), mCursor.getInt(mCursor.getColumnIndex(KEY_ANO)), mCursor.getString(mCursor.getColumnIndex(KEY_NOME)));
return carros;
}
else
return null;
}
public Carro getCarro(long id) throws SQLException {
Cursor mCursor = mDb.query(true, DATABASE_TABLE_CARROS, null, KEY_ID + "=" + id, null, null, null, null, null);
if(mCursor != null){
mCursor.moveToFirst();
return new Carro(id, mCursor.getInt(mCursor.getColumnIndex(KEY_ANO)), mCursor.getString(mCursor.getColumnIndex(KEY_NOME)));
}
else
return null;
}

Podemos ainda fazer umas modificações para que os métodos createCarro, deleteCarro e updateCarro aceitem um objeto Carro como parâmetro, assim teremos um banco de dados que está diretamente relacionado a objetos, mas isto deixarei por sua conta. Usar objetos diretamente relacionados ao banco de dados tem suas vantagens e desvantagens. Tenha sempre em mente que é melhor fazer seleções a nível de busca no banco de dados do que pegar todos os registros e fazer isso no código da aplicação.

É isso, qualquer dúvida só perguntar. 


15 comentários:

Murilo Ângelo at 19 de julho de 2012 02:26 disse...

Usando o método getAllcarros() eu obtenho de retorno um cursor, certo, como eu faria para listar esses dados em um ListView?

Gustavo Carvalho at 20 de julho de 2012 11:02 disse...

Você pode usar um SimpleCursorAdapter na ListView da seguinte forma:

mListView.setAdapter(new SimpleCursorAdapter(this, R.layout.item_lista, mCursor, new String[]{ KEY_NOME, KEY_ANO }, new int[]{ R.id.text_nome, R.id.text_ano} ));

O SimpleCursorAdapter recebe com parâmetros respectivamente: Context, o id do layout que representa um item da lista (pode ser por exemplo android.R.layout.simple_list_item_2), o cursor que você obteve com o getAllCarros(), uma lista de string com o nome das colunas que você quer exibir na ordem e uma lista de ids dos elementos do layout onde esses itens serão exibidos na ordem. Por exemplo, se usasse o layout android.R.layout.simple_list_item_2, poderia usar como lista de id o seguinte: new int[] {android.R.id.text1, android.R.id.text2}.

Frederico Brigatte at 27 de julho de 2012 14:05 disse...

Como ficaria uma consulta por nome do carro?

frederico.brigatte@gnail.com

Unknown at 11 de agosto de 2012 11:23 disse...

É possível fazer busca numa ListView com dados do SQLite?

frederico.brigatte@gmail.com

Gustavo Carvalho at 11 de agosto de 2012 15:47 disse...

Sim, mas acho que para uma solução mais otimizada o ideal é implementar seu próprio adapter ao invés de utilizar o SimpleCursorAdapter. Basta implementar um BaseAdapter (estende-lo) e fazer com que mostre apenas itens que batam com a pesquisa.

Andreia at 26 de março de 2013 16:43 disse...

Execlente Post! Mas, tenho uma duvida como seria com mais de uma tabela e relacionamento, ou seja, foreign key. Não achei nada na net ainda.

Gustavo Carvalho at 29 de abril de 2013 15:38 disse...

@Andreia: Um exemplo que estou desenvolvendo agora:

private static final String[] DATABASE_CREATE = {"create table " + DATABASE_TABLE_CATEGORIES + " ("
+ KEY_ID + " integer primary key autoincrement, "
+ KEY_NAME + " text not null, "
+ KEY_LANGUAGE + " integer not null, "
+ KEY_PLUS + " integer not null default '0');",

"create table " + DATABASE_TABLE_MSGS + " ("
+ KEY_ID + " integer primary key autoincrement, "
+ KEY_MENSAGEM + " text not null, "
+ KEY_PLUS + " integer not null default '0', "
+ KEY_NEW + " integer not null default '1', "
+ KEY_USED + " integer not null default '0', "
+ KEY_FAVORITE + " integer not null default '0', "
+ KEY_CATEGORY + " integer not null, "
+ "foreign key ( " + KEY_CATEGORY + " ) references " + DATABASE_TABLE_CATEGORIES + " ( " + KEY_ID + " ));"

@Override
public void onCreate(SQLiteDatabase db){
for(int i = 0; i < DATABASE_CREATE.length; i++)
db.execSQL(DATABASE_CREATE[i]);
}

};

Anônimo at 2 de maio de 2013 19:05 disse...

Minha dúvida é como eu faço para criar um banco de de Dados com informações só para serem acessadas(consultadas)

Por exemplo tenho 3 spinners e um botão. Vou colocar um exemplo

na 1º Spinner eu teria opções de escola, hospitais, praças (Lugares Públicos)...
na 2º Spinner eu teria as opções de zonas da cidade,( zona 1, zona2, zona3..)
na 3º spinner os principais os bairros da cidade( bairro A, bairro B...)

se a spinner 1 eu escolher Escola
e a spinner 3 eu escolher o Bairro A,
e a pessoa clicar no botão pesquisar
como eu faço para na próxima tela apresentar todas as escolas do Bairro A ??


Não é pra inserir nada, queria saber como faz um banco de dados apenas para consultar dados inseridos!
De modo que quando alguém selecionar o Bairro A, na tela seguinte faria uma query sobre esses dados (ex. select * from escola where bairro = 'A')
Como eu faço isso??

Fred at 7 de junho de 2013 10:26 disse...

Como faria usando DAO? Separando a parte de criação das tabelas e banco com a parte de CRUD?

frederico.brigatte@gmail.com

Unknown at 24 de agosto de 2013 18:37 disse...

Poderia postar um exemplo de actionbar com tabs e sqlite?

Rodrigo Sozi at 30 de setembro de 2013 19:22 disse...

O SQLite armazena a informação na memoria interna ou na memoria externa?

Curta Oferece-se at 2 de outubro de 2013 15:11 disse...

quero criar uma tabela para armazenar imagem e texto (nome, informaçoes, email e telefone)
e poder gerar uma lista com essas linhas, e poder clicar para ver a inforção inteira dos campos

Unknown at 4 de dezembro de 2013 18:04 disse...

Tenho um banco sqlite com dados cadastrados externamente. Como utilizar esse banco assim que o app é instalado? Ele cria o banco vazio. Preciso que ao instalar esse apk, mantenha os dados do banco.

Anônimo at 30 de junho de 2014 11:07 disse...

Olá. Eu tenho uma aplicação simples que usa sqlite. Quando executo no emulador, consigo acessar o arquivo .db. Porém, eu gerei o apk, instalei em um dispositivo, só que agora não encontro o arquivo .db. Como eu faço?

Anônimo at 30 de junho de 2014 12:09 disse...

Olá. Eu tenho uma aplicação simples que usa sqlite. Quando executo no emulador, consigo acessar o arquivo .db. Porém, eu gerei o apk, instalei em um dispositivo, só que agora não encontro o arquivo .db. Como eu faço?

Postar um comentário

 
© 2011 Tutoriandroid | Recode by Ardhiansyam | Based on Android Developers Blog