Almacenar datos de forma local en el dispositivo es indispensable para crear aplicaciones offline-first, guardar cachés de respuestas de red o gestionar información confidencial sin depender en todo momento de la cobertura de internet. En el ecosistema Android, el motor que sustenta el almacenamiento relacional es SQLite, y su evolución moderna y recomendada por Google es la biblioteca Room Database de Android Jetpack.
1. Conceptos Fundamentales de SQLite en Android
Cada aplicación en Android dispone de un espacio privado en la memoria interna (en la ruta /data/data/[nombre_del_paquete]/databases/) donde se crean los ficheros de base de datos protegidos por el sandboxing del sistema operativo.
De forma nativa, la comunicación tradicional se realiza mediante la clase auxiliar SQLiteOpenHelper y el objeto SQLiteDatabase:
- onCreate(): Se ejecuta la primera vez para crear las tablas mediante sentencias DDL (CREATE TABLE).
- onUpgrade(): Permite gestionar migraciones de esquema cuando actualizas la versión de la base de datos.
- getWritableDatabase() / getReadableDatabase(): Proporciona la instancia para realizar lecturas o escrituras.
- Cursor: Puntero a las filas obtenidas mediante
rawQuery(). Recorrer un cursor requiere métodos comomoveToNext(),getColumnIndexOrThrow()y cerrar siempre el cursor para evitar fugas de memoria (memory leaks).
Puedes descargar el código del proyecto de ejemplo clásico en mi repositorio de GitHub: SQLiteDemo de Walkiria Apps.
2. La Arquitectura Moderna: Implementar Room en Kotlin
Para proyectos nuevos, el estándar de la industria es utilizar Room. Se compone de tres pilares:
1. La Entidad (@Entity): Define la tabla y sus columnas como una data class de Kotlin:
@Entity(tableName = "usuarios")
data class Usuario(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val nombre: String,
val email: String
)
2. El DAO (@Dao – Data Access Object): Define las consultas y operaciones con validación SQL en tiempo de compilación y soporte de Corrutinas:
@Dao
interface UsuarioDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertar(usuario: Usuario)
@Query("SELECT * FROM usuarios ORDER BY id DESC")
fun obtenerTodos(): Flow<List<Usuario>>
@Delete
suspend fun eliminar(usuario: Usuario)
}
3. La Base de Datos (@Database): La clase abstracta que extiende de RoomDatabase:
@Database(entities = [Usuario::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun usuarioDao(): UsuarioDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"mi_base_de_datos.db"
).build()
INSTANCE = instance
instance
}
}
}
}
Flow<List<Usuario>>, si cualquier parte de tu app inserta o actualiza un registro en la base de datos, tu pantalla (RecyclerView o Jetpack Compose) se actualizará automáticamente en tiempo real sin tener que recargar manualmente los datos.