Este artigo explora o desenvolvimento de uma aplicação Android simples que utiliza SQLite para persistência de dados local, SharedPreferences para gerenciamento de sessão e um padrão para gerir a pilha de Activities.
Arquitetura Geral e Camadas de Dados
A aplicação segue uma estrutura comum: Activitys (UI), uma camada de Acesso a Dados (DAOs), uma clase auxiliar para o banco de dados (DbHelper) e classes de modelo (Entidades).
Gerenciamento do Banco de Dados com DbHelper
A classe DbHelper estende SQLiteOpenHelper, sendo responsável pela criação e atualização do esquema do banco de dados. Ela implementa um padrão Singleton para garantir uma única instância.
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "app_data.db";
private static final int DB_VERSION = 1;
private static DatabaseHelper sInstance;
public static synchronized DatabaseHelper getInstance(Context ctx) {
if (sInstance == null) {
sInstance = new DatabaseHelper(ctx.getApplicationContext());
}
return sInstance;
}
private DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS users (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"username TEXT UNIQUE NOT NULL, " +
"password_hash TEXT NOT NULL)");
// Outras tabelas (ex: products, orders) seriam criadas aqui.
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Lógica de migração entre versões do banco.
onCreate(db);
}
}
Camada de Acesso a Dados (DAO)
Os DAOs encapsulam a lógica de acesso ao banco de dados. Utilizam ContentValues para inserções e atualizações, e Cursor para consultas.
public class ProductDao {
private final SQLiteDatabase database;
public ProductDao(Context context) {
DatabaseHelper helper = DatabaseHelper.getInstance(context);
this.database = helper.getReadableDatabase();
}
public long insert(Product product) {
ContentValues insertValues = new ContentValues();
insertValues.put("name", product.getName());
insertValues.put("price", product.getPrice());
return database.insert("products", null, insertValues);
}
public Cursor fetchAll() {
return database.query("products", null, null, null, null, null, "name ASC");
}
}
Utilizando SimpleCursorAdapter para Exibir Dados
Para exibir dados de um Cursor em uma ListView, o SimpleCursorAdapter mapeia colunas do banco para Views do layout.
// Em uma Activity ou Fragment
Cursor dataCursor = productDao.fetchAll();
String[] fromColumns = {"name", "price"};
int[] toViews = {R.id.tvProductName, R.id.tvProductPrice};
SimpleCursorAdapter adapter = new SimpleCursorAdapter(
this,
R.layout.list_item_product,
dataCursor,
fromColumns,
toViews,
0); // Flags como FLAG_REGISTER_CONTENT_OBSERVER podem ser usadas
listView.setAdapter(adapter);
Gerenciamento de Sessão com SharedPreferences
O SharedPreferences é ideal para salvar dados simples de configuração, como o estado de login. Um arquivo (ex: user_session.xml) armazena pares chave-valor.
// Ao fazer login com sucesso
SharedPreferences prefs = getSharedPreferences("user_session", MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("LOGGED_IN_USER", "usuario_exemplo");
editor.putBoolean("IS_LOGGED_IN", true);
editor.apply(); // Usar apply() para gravação assíncrona
// Em outra Activity para verificar o login
SharedPreferences prefs = getSharedPreferences("user_session", MODE_PRIVATE);
boolean isLoggedIn = prefs.getBoolean("IS_LOGGED_IN", false);
if (!isLoggedIn) {
// Redirecionar para a tela de login
}
Gerenciamento da Pilha de Activities (Activity Stack)
Uma classe Application personalizada pode manter uma lista de Activities ativas para facilitar o encerramento controlado da aplicação.
public class App extends Application {
private static App instance;
private final List<activity> activeActivities = new ArrayList<>();
@Override
public void onCreate() {
super.onCreate();
instance = this;
}
public static App getInstance() {
return instance;
}
public void registerActivity(Activity activity) {
activeActivities.add(activity);
}
public void unregisterActivity(Activity activity) {
activeActivities.remove(activity);
}
public void exitApplication() {
for (Activity activity : activeActivities) {
if (!activity.isFinishing()) {
activity.finish();
}
}
activeActivities.clear();
System.exit(0);
}
}
// Em cada Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
App.getInstance().registerActivity(this);
// ...
}
@Override
protected void onDestroy() {
super.onDestroy();
App.getInstance().unregisterActivity(this);
}
</activity>
Operações CRUD Diretas com SQLiteDatabase
Também é possível executar operações CRUD diretamente usando os métodos da classe SQLiteDatabase.
SQLiteDatabase db = DatabaseHelper.getInstance(this).getWritableDatabase();
// INSERT
ContentValues values = new ContentValues();
values.put("name", "Novo Produto");
values.put("price", 19.99);
long newRowId = db.insert("products", null, values);
// UPDATE
ContentValues updateValues = new ContentValues();
updateValues.put("price", 24.99);
int rowsAffected = db.update("products", updateValues, "name = ?", new String[]{"Novo Produto"});
// DELETE
int deletedRows = db.delete("products", "id = ?", new String[]{"1"});
// QUERY com rawQuery
Cursor cursor = db.rawQuery("SELECT * FROM products WHERE price > ?", new String[]{"20.00"});
Considerações de Design
O uso de DAOs abstrai os detalhes de implementação do banco de dados, tornando o código mais limpo e testável. O SharedPreferences deve ser usado apenas para dados leves, enquanto SQLite é indicado para dados estruturados e complexos. O gerenciamento da pilha de Activities via classe Application ajuda a controlar o ciclo de vida da aplicação e realizar limpeza de recursos.