Tutorial Android #11 – Persistência em Banco de Dados

Padrão

Olá leitores! No post de hoje, vamos criar um sistema de persistência para a nossa Lista de Restaurantes. Assim, os restaurantes cadastrados serão mantidos a cada execução do aplicativo.

O sistema Android nos fornece nativamente as opções de persistir dados utilizando arquivos ou em banco de dados, utilizando o SQLite. Se você não conhece o projeto, é interessante dar uma lida sobre ele. É um banco de dados bastante leve, que nos permite facilmente trabalhar com SQL sobre um arquivo.

Neste tutorial, estou assumindo que você tenha um conhecimento básico em SQL (apesar de eu nunca ter tratado deste assunto aqui no blog). Se você nunca mexeu com isso, não se preocupe, pois os conceitos não são complicados de entender :).

Bom, começando o nosso tutorial, vamos criar uma classe que gerenciará a criação e abertura do nosso banco de dados. Vamos chamá-la de GerenciadorRestaurantes. Coloque-a no pacote net.rafaeltoledo.restaurante.

package net.rafaeltoledo.restaurante;

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

public class GerenciadorRestaurantes extends SQLiteOpenHelper {

	private static final String NOME_BANCO = "restaurantes.db";
	private static final int VERSAO_SCHEMA = 1;

	public GerenciadorRestaurantes(Context context) {
		super(context, NOME_BANCO, null, VERSAO_SCHEMA);
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT,"
				+ " nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT);");

	}

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

	}
}

A princípio, definimos que nosso banco de dados será armazenado no arquivo restaurantes.db e que utilizaremos a primeira versão do schema do banco. Neste ponto o projeto ainda deve compilar sem problemas.

Em seguida, vamos implementar o método onCreate() para que ele crie o nosso banco de dados.

@Override
public void onCreate(SQLiteDatabase db) {
	db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT," +
			" nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT);");
}

Neste trecho simplesmente criamos a tabela restaurantes com seus campos. O método onUpgrade() não será útil para nós por enquanto. Em um aplicativo real, você poderia implementá-lo para fazer backup dos dados em uma tabela temporária, atualizar a estrutura do banco e então retornar os dados.

O próximo passo é remover partes do código da classe ListaRestaurantes que não nos serão úteis daqui pra frente (como os trechos que manipulam a nossa barra de progresso). As primeiras exclusões são os atributos estaAtivo  e progresso. Em seguida, podemos remover a chamada ao método requestWindowFeature() dentro do método onCreate(). Podemos também excluir as implementações dos métodos onPause(), onResume(), onCreateOptionsMenu() e onOptionsItemSelected(). Por fim, podemos excluir também os métodos iniciarTarefa(), fazerAlgoDemorado() e a nossa tarefaLonga.

A classe GerenciadorRestaurantes será a nossa ponte entre a aplicação e o banco de dados. Dessa forma, vamos criar um atributo na classe ListaRestaurantes chamado gerenciador do tipo GerenciadorRestaurante.

GerenciadorRestaurantes gerenciador;

Lá no método onCreate(), após a chamada a setContentView(), vamos então instanciar o atributo:

gerenciador = new GerenciadorRestaurantes(this);

Complementando, implemente o método onDestroy() na classe ListaRestaurantes.

@Override
public void onDestroy() {
	super.onDestroy();
	gerenciador.close();
}

Nós vamos agora, substituir nosso objeto de modelo (e seu ArrayList associado) pelo banco de dados, utilizando também a classe Cursor do Android para controlar as instâncias. Primeiramente, vamos adicionar o método inserir() na classe GerenciadorRestaurantes:

public void inserir(String nome, String endereco, String tipo, String anotacoes) {
	ContentValues valores = new ContentValues();

	valores.put("nome", nome);
	valores.put("endereco", endereco);
	valores.put("tipo", tipo);
	valores.put("anotacoes", anotacoes);

	getWritableDatabase().insert("restaurantes", "nome", valores);
}

Neste método, recebemos os valores individuais dos campos que compõem a classe Restaurante e adicionamos a um objeto ContentValues, relacionando os valores com as colunas da tabela do nosso banco de dados. Por fim, obtemos uma instância do banco para escrita e inserimos os valores na tabela restaurantes.

Agora, devemos realizar a chamada a este método ao pressionarmos o botão Salvar em nosso formulário (onSave).

private OnClickListener onSave = new OnClickListener() {

	public void onClick(View arg0) {
		String tipo = null;

		switch (tipos.getCheckedRadioButtonId()) {
		case R.id.rodizio:
			tipo = "rodizio";
			break;
		case R.id.fast_food:
			tipo = "fast_food";
			break;
		case R.id.a_domicilio:
			tipo = "a_domicilio";
			break;
		}

		gerenciador.inserir(nome.getText().toString(),
				endereco.getText().toString(), tipo,
				anotacoes.getText().toString());
	}
};

Em seguida, vamos fazer com que a listagem de restaurantes seja realizada a partir do nosso banco de dados. Se você já mexeu com banco de dados no Java, já deve ter visto o funcionamento de um ResultSet. Ele armazena o conteúdo de uma consulta ao banco de dados. No Android, utilizamos a classe Cursor que tem funcionamento semelhante.

Assim, vamos criar um método na classe GerenciadorRestaurantes para obter a lista de restaurantes salvos no banco. Vamos implementar o método obterTodos():

public Cursor obterTodos() {
	return getReadableDatabase().rawQuery("select id, nome, endereco, tipo, " +
			"anotacoes FROM restaurantes ORDER BY nome", null);
}

Precisaremos também de métodos que nos forneçam acesso a determinados campos do Cursor. Dessa forma, adicione estes métodos à classe GerenciadorRestaurantes:

public String obterNome(Cursor c) {
	return c.getString(1);
}

public String obterEndereco(Cursor c) {
	return c.getString(2);
}

public String obterTipo(Cursor c) {
	return c.getString(3);
}

public String obterAnotacoes(Cursor c) {
	return c.getString(4);
}

Na nossa implementação atual, a classe Adaptador estende a classe ArrayAdapter, de forma que ela não conseguirá manipular os dados contidos no Cursor. Assim, modificaremos sua implementação para, então, estender não mais ArrayAdapter, mas sim CursorAdapter.

class AdaptadorRestaurante extends CursorAdapter {
	AdaptadorRestaurante(Cursor c) {
		super(ListaRestaurantes.this, c);
	}

	@Override
	public void bindView(View view, Context context, Cursor cursor) {
		ArmazenadorRestaurante armazenador = (ArmazenadorRestaurante) view.getTag();
		armazenador.popularFormulario(cursor, gerenciador);
	}

	@Override
	public View newView(Context context, Cursor cursor, ViewGroup parent) {
		LayoutInflater inflater = getLayoutInflater();
		View linha = inflater.inflate(R.layout.linha, parent, false);
		ArmazenadorRestaurante armazenador = new ArmazenadorRestaurante(linha);
		linha.setTag(armazenador);
		return linha;
	}
}

Como pode ser percebido, a classe ArmazenadorRestaurante também necessita de alguns ajustes, para manipular o objeto da classe Cursor. Mas antes, vamos modificar o atributo listaRestaurantes do tipo List para Cursor.

Cursor listaRestaurantes;

Agora, no método onCreate(), substitua o código que populava o antigo ArrayList por este:

listaRestaurantes = gerenciador.obterTodos();
startManagingCursor(listaRestaurantes);
adaptador = new AdaptadorRestaurante(listaRestaurantes);
lista.setAdapter(adaptador);

Prosseguindo (calma, já tá quase acabando!), vamos atualizar a classe ArmazenadorRestaurante para trabalhar com o Cursor:

static class ArmazenadorRestaurante {
	private TextView nome = null;
	private TextView endereco = null;
	private ImageView icone = null;

	ArmazenadorRestaurante(View linha) {
		nome = (TextView) linha.findViewById(R.id.titulo);
		endereco = (TextView) linha.findViewById(R.id.endereco);
		icone = (ImageView) linha.findViewById(R.id.icone);
	}

	void popularFormulario(Cursor c, GerenciadorRestaurantes gerenciador) {
		nome.setText(gerenciador.obterNome(c));
		endereco.setText(gerenciador.obterEndereco(c));

		if (gerenciador.obterTipo(c).equals("rodizio")) {
			icone.setImageResource(R.drawable.rodizio);
		} else if (gerenciador.obterTipo(c).equals("fast_food")) {
			icone.setImageResource(R.drawable.fast_food);
		} else {
			icone.setImageResource(R.drawable.entrega);
		}
	}
}

Por fim, vamos modificar todas as referências ao ArrayList que tínhamos no nosso onListClick.

private OnItemClickListener onListClick = new OnItemClickListener() {
	public void onItemClick(AdapterView<?> parent, View view, int position,
			long id) {
		listaRestaurantes.moveToPosition(position);
		nome.setText(gerenciador.obterNome(listaRestaurantes));
		endereco.setText(gerenciador.obterEndereco(listaRestaurantes));
		anotacoes.setText(gerenciador.obterAnotacoes(listaRestaurantes));

		if (gerenciador.obterTipo(listaRestaurantes).equals("rodizio")) {
			tipos.check(R.id.rodizio);
		} else if (gerenciador.obterTipo(listaRestaurantes).equals("fast_food")) {
			tipos.check(R.id.fast_food);
		} else {
			tipos.check(R.id.a_domicilio);
		}

		getTabHost().setCurrentTab(1);
	}
};

Como último passo precisamos adicionar uma linha para que a lista seja atualizada a cada inserção. Insira a seguinte linha após a inserção lá no onSave:

listaRestaurantes.requery();

Pronto! Você já pode executar a sua versão persistente do Lista de Restaurantes!

Segue a listagem completa da classe ListaRestaurantes:

package net.rafaeltoledo.restaurante;

import net.rafaeltoledo.restaurante.model.Restaurante;
import android.app.TabActivity;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.CursorAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RadioGroup;
import android.widget.TabHost.TabSpec;
import android.widget.TextView;

public class ListaRestaurantes extends TabActivity {

	Cursor listaRestaurantes;
	AdaptadorRestaurante adaptador = null;
	Restaurante atual = null;

	EditText nome = null;
	EditText endereco = null;
	EditText anotacoes = null;
	RadioGroup tipos = null;
	GerenciadorRestaurantes gerenciador;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		gerenciador = new GerenciadorRestaurantes(this);

		nome = (EditText) findViewById(R.id.nome);
		endereco = (EditText) findViewById(R.id.end);
		anotacoes = (EditText) findViewById(R.id.anotacoes);
		tipos = (RadioGroup) findViewById(R.id.tipos);

		Button salvar = (Button) findViewById(R.id.salvar);
		salvar.setOnClickListener(onSave);

		ListView lista = (ListView) findViewById(R.id.restaurantes);

		listaRestaurantes = gerenciador.obterTodos();
		startManagingCursor(listaRestaurantes);
		adaptador = new AdaptadorRestaurante(listaRestaurantes);
		lista.setAdapter(adaptador);

		TabSpec descritor = getTabHost().newTabSpec("tag1");
		descritor.setContent(R.id.restaurantes);
		descritor.setIndicator("Lista",
				getResources().getDrawable(R.drawable.lista));
		getTabHost().addTab(descritor);

		descritor = getTabHost().newTabSpec("tag2");
		descritor.setContent(R.id.detalhes);
		descritor.setIndicator("Detalhes",
				getResources().getDrawable(R.drawable.restaurante));
		getTabHost().addTab(descritor);

		getTabHost().setCurrentTab(0);

		lista.setOnItemClickListener(onListClick);
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		gerenciador.close();
	}

	private OnItemClickListener onListClick = new OnItemClickListener() {
		public void onItemClick(AdapterView<?> parent, View view, int position,
				long id) {
			listaRestaurantes.moveToPosition(position);
			nome.setText(gerenciador.obterNome(listaRestaurantes));
			endereco.setText(gerenciador.obterEndereco(listaRestaurantes));
			anotacoes.setText(gerenciador.obterAnotacoes(listaRestaurantes));

			if (gerenciador.obterTipo(listaRestaurantes).equals("rodizio")) {
				tipos.check(R.id.rodizio);
			} else if (gerenciador.obterTipo(listaRestaurantes).equals("fast_food")) {
				tipos.check(R.id.fast_food);
			} else {
				tipos.check(R.id.a_domicilio);
			}

			getTabHost().setCurrentTab(1);
		}
	};

	private OnClickListener onSave = new OnClickListener() {

		public void onClick(View arg0) {
			String tipo = null;

			switch (tipos.getCheckedRadioButtonId()) {
			case R.id.rodizio:
				tipo = "rodizio";
				break;
			case R.id.fast_food:
				tipo = "fast_food";
				break;
			case R.id.a_domicilio:
				tipo = "a_domicilio";
				break;
			}

			gerenciador.inserir(nome.getText().toString(), endereco.getText()
					.toString(), tipo, anotacoes.getText().toString());
			listaRestaurantes.requery();
		}
	};

	class AdaptadorRestaurante extends CursorAdapter {
		AdaptadorRestaurante(Cursor c) {
			super(ListaRestaurantes.this, c);
		}

		@Override
		public void bindView(View view, Context context, Cursor cursor) {
			ArmazenadorRestaurante armazenador = (ArmazenadorRestaurante) view.getTag();
			armazenador.popularFormulario(cursor, gerenciador);
		}

		@Override
		public View newView(Context context, Cursor cursor, ViewGroup parent) {
			LayoutInflater inflater = getLayoutInflater();
			View linha = inflater.inflate(R.layout.linha, parent, false);
			ArmazenadorRestaurante armazenador = new ArmazenadorRestaurante(linha);
			linha.setTag(armazenador);
			return linha;
		}
	}

	static class ArmazenadorRestaurante {
		private TextView nome = null;
		private TextView endereco = null;
		private ImageView icone = null;

		ArmazenadorRestaurante(View linha) {
			nome = (TextView) linha.findViewById(R.id.titulo);
			endereco = (TextView) linha.findViewById(R.id.endereco);
			icone = (ImageView) linha.findViewById(R.id.icone);
		}

		void popularFormulario(Cursor c, GerenciadorRestaurantes gerenciador) {
			nome.setText(gerenciador.obterNome(c));
			endereco.setText(gerenciador.obterEndereco(c));

			if (gerenciador.obterTipo(c).equals("rodizio")) {
				icone.setImageResource(R.drawable.rodizio);
			} else if (gerenciador.obterTipo(c).equals("fast_food")) {
				icone.setImageResource(R.drawable.fast_food);
			} else {
				icone.setImageResource(R.drawable.entrega);
			}
		}
	}
}

… e GerenciadorRestaurantes …

package net.rafaeltoledo.restaurante;

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

public class GerenciadorRestaurantes extends SQLiteOpenHelper {

	private static final String NOME_BANCO = "restaurantes.db";
	private static final int VERSAO_SCHEMA = 1;

	public GerenciadorRestaurantes(Context context) {
		super(context, NOME_BANCO, null, VERSAO_SCHEMA);
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		db.execSQL("CREATE TABLE restaurantes (_id INTEGER PRIMARY KEY AUTOINCREMENT," +
				" nome TEXT, endereco TEXT, tipo TEXT, anotacoes TEXT);");
	}

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

	}

	public void inserir(String nome, String endereco, String tipo, String anotacoes) {
		ContentValues valores = new ContentValues();

		valores.put("nome", nome);
		valores.put("endereco", endereco);
		valores.put("tipo", tipo);
		valores.put("anotacoes", anotacoes);

		getWritableDatabase().insert("restaurantes", "nome", valores);
	}

	public Cursor obterTodos() {
		return getReadableDatabase().rawQuery("select _id, nome, endereco, tipo, " +
				"anotacoes FROM restaurantes ORDER BY nome", null);
	}

	public String obterNome(Cursor c) {
		return c.getString(1);
	}

	public String obterEndereco(Cursor c) {
		return c.getString(2);
	}

	public String obterTipo(Cursor c) {
		return c.getString(3);
	}

	public String obterAnotacoes(Cursor c) {
		return c.getString(4);
	}
}

Se você perdeu alguma parte, pode baixar o projeto aqui.

Até a próxima! 🙂

7 comentários sobre “Tutorial Android #11 – Persistência em Banco de Dados

  1. Amigo, show de bola o seu tutorial.
    Pelo que olhei o seu código é possível listar todas as entradas do banco de dados e ainda personalizar o layout onde elas serão listadas, o que é exatamente o que estou procurando.

    Valeu!

  2. Fabiano

    Boa tarde, acho que estou fazendo alguma coisa errada. Não estou conseguindo executar o Select do exemplo. Sempre aparece um erro dizendo que a coluna _id não existe. Poderia me ajudar por favor?

    • Olá Fabiano! Confira o método onCreate(). Em uma falha minha, neste método eu não havia inserido o _ antes do id. Já corrigi no post.

      Obrigado por comentar! Qualquer dúvida, só avisar! 🙂

  3. Eduardoxvii

    Tire-me uma dúvida desse trecho: getWritableDatabase().insert(“restaurantes”, “nome”, valores);

    “nome”: Porque você colocou a string nome nesse parametro? O que ela faz?

    De qualquer forma obrigado

  4. Francisco

    Olá, parabés pelos tuts estão a ajudar-me bastante. Seria interessate um tutorial sobre login.Ou seja, para entrar na aplicação era preciso login.

Deixe uma resposta