Almacenamiento en una base de datos SQLite¶
Hasta ahora hemos visto dos formas de persistencia básica (archivos de texto y SharedPreferences). En este apartado introducimos SQLite, una base de datos embebida muy popular en Android.
SQLite es una base de datos Open Source y destaca porque:
- No requiere configuración previa.
- No existe un servidor de base de datos en un proceso separado.
- Permite almacenar datos estructurados y consultarlos con SQL.
Note
Para proyectos profesionales actuales se recomienda Room (AndroidX) como capa de abstracción sobre SQLite.
En este tema trabajaremos con SQLite nativo (SQLiteOpenHelper) por su valor formativo y para comprender los fundamentos.
Enunciado del ejercicio: gestión de artículos¶
Se debe crear una tabla articulos con los campos:
codigo(entero, clave primaria)descripcion(texto)precio(real)
La aplicación debe permitir:
- Alta (carga) de artículo.
- Consulta por código.
- Consulta por descripción.
- Borrado por código.
- Modificación de descripción y precio.
1 Clase SQLiteOpenHelper¶
Crearemos una clase que herede de SQLiteOpenHelper. Esta clase se encarga de:
- Crear la base de datos (y sus tablas) la primera vez.
- Gestionar cambios de versión (método
onUpgrade).
AdminSQLiteOpenHelper.kt¶
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
class AdminSQLiteOpenHelper(context: Context) :
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(
"""CREATE TABLE articulos(
codigo INTEGER PRIMARY KEY,
descripcion TEXT NOT NULL,
precio REAL NOT NULL
)"""
)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
// En un proyecto real, aquí aplicarías migraciones.
// Para un ejemplo didáctico sencillo:
db.execSQL("DROP TABLE IF EXISTS articulos")
onCreate(db)
}
companion object {
private const val DB_NAME = "administracion.db"
private const val DB_VERSION = 1
}
}
2 Interfaz visual (XML)¶
La pantalla utiliza:
EditTextpara código, descripción y precio- 5 botones para cada operación (alta, consultas, baja y modificación)
activity_main.xml¶
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<EditText
android:id="@+id/etCodigo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="Ingrese código"
android:inputType="number"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<EditText
android:id="@+id/etDescripcion"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="Ingrese descripción"
android:inputType="text"
app:layout_constraintTop_toBottomOf="@id/etCodigo"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="8dp"/>
<EditText
android:id="@+id/etPrecio"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="Ingrese precio"
android:inputType="numberDecimal"
app:layout_constraintTop_toBottomOf="@id/etDescripcion"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="8dp"/>
<Button
android:id="@+id/btnAlta"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Alta"
app:layout_constraintTop_toBottomOf="@id/etPrecio"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"/>
<Button
android:id="@+id/btnConsultaCodigo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Consulta por código"
app:layout_constraintTop_toBottomOf="@id/btnAlta"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="8dp"/>
<Button
android:id="@+id/btnConsultaDescripcion"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Consulta por descripción"
app:layout_constraintTop_toBottomOf="@id/btnConsultaCodigo"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="8dp"/>
<Button
android:id="@+id/btnBaja"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Baja por código"
app:layout_constraintTop_toBottomOf="@id/btnConsultaDescripcion"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="8dp"/>
<Button
android:id="@+id/btnModificacion"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Modificación"
app:layout_constraintTop_toBottomOf="@id/btnBaja"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="8dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
3 Activity (Kotlin) con operaciones CRUD¶
En 2025 se recomienda evitar concatenar valores directamente en SQL (por errores e inyección).
Por ello, las consultas se hacen con parámetros (?) y selectionArgs.
MainActivity.kt¶
import android.content.ContentValues
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var etCodigo: EditText
private lateinit var etDescripcion: EditText
private lateinit var etPrecio: EditText
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
etCodigo = findViewById(R.id.etCodigo)
etDescripcion = findViewById(R.id.etDescripcion)
etPrecio = findViewById(R.id.etPrecio)
findViewById<Button>(R.id.btnAlta).setOnClickListener { alta() }
findViewById<Button>(R.id.btnConsultaCodigo).setOnClickListener { consultaPorCodigo() }
findViewById<Button>(R.id.btnConsultaDescripcion).setOnClickListener { consultaPorDescripcion() }
findViewById<Button>(R.id.btnBaja).setOnClickListener { bajaPorCodigo() }
findViewById<Button>(R.id.btnModificacion).setOnClickListener { modificacion() }
}
private fun alta() {
val codigo = etCodigo.text.toString().trim()
val descripcion = etDescripcion.text.toString().trim()
val precio = etPrecio.text.toString().trim()
if (codigo.isEmpty() || descripcion.isEmpty() || precio.isEmpty()) {
Toast.makeText(this, "Completa código, descripción y precio", Toast.LENGTH_SHORT).show()
return
}
val admin = AdminSQLiteOpenHelper(this)
val db = admin.writableDatabase
val registro = ContentValues().apply {
put("codigo", codigo.toInt())
put("descripcion", descripcion)
put("precio", precio.toDouble())
}
val id = db.insert("articulos", null, registro)
db.close()
if (id != -1L) {
limpiarCampos()
Toast.makeText(this, "Artículo cargado", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "No se pudo cargar (¿código duplicado?)", Toast.LENGTH_SHORT).show()
}
}
private fun consultaPorCodigo() {
val codigo = etCodigo.text.toString().trim()
if (codigo.isEmpty()) {
Toast.makeText(this, "Ingresa un código", Toast.LENGTH_SHORT).show()
return
}
val admin = AdminSQLiteOpenHelper(this)
val db = admin.readableDatabase
val cursor = db.query(
"articulos",
arrayOf("descripcion", "precio"),
"codigo = ?",
arrayOf(codigo),
null, null, null
)
if (cursor.moveToFirst()) {
etDescripcion.setText(cursor.getString(0))
etPrecio.setText(cursor.getDouble(1).toString())
} else {
Toast.makeText(this, "No existe un artículo con ese código", Toast.LENGTH_SHORT).show()
}
cursor.close()
db.close()
}
private fun consultaPorDescripcion() {
val descripcion = etDescripcion.text.toString().trim()
if (descripcion.isEmpty()) {
Toast.makeText(this, "Ingresa una descripción", Toast.LENGTH_SHORT).show()
return
}
val admin = AdminSQLiteOpenHelper(this)
val db = admin.readableDatabase
val cursor = db.query(
"articulos",
arrayOf("codigo", "precio"),
"descripcion = ?",
arrayOf(descripcion),
null, null, null
)
if (cursor.moveToFirst()) {
etCodigo.setText(cursor.getInt(0).toString())
etPrecio.setText(cursor.getDouble(1).toString())
} else {
Toast.makeText(this, "No existe un artículo con esa descripción", Toast.LENGTH_SHORT).show()
}
cursor.close()
db.close()
}
private fun bajaPorCodigo() {
val codigo = etCodigo.text.toString().trim()
if (codigo.isEmpty()) {
Toast.makeText(this, "Ingresa un código", Toast.LENGTH_SHORT).show()
return
}
val admin = AdminSQLiteOpenHelper(this)
val db = admin.writableDatabase
val cant = db.delete("articulos", "codigo = ?", arrayOf(codigo))
db.close()
if (cant == 1) {
limpiarCampos()
Toast.makeText(this, "Artículo borrado", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "No existe un artículo con ese código", Toast.LENGTH_SHORT).show()
}
}
private fun modificacion() {
val codigo = etCodigo.text.toString().trim()
val descripcion = etDescripcion.text.toString().trim()
val precio = etPrecio.text.toString().trim()
if (codigo.isEmpty() || descripcion.isEmpty() || precio.isEmpty()) {
Toast.makeText(this, "Completa código, descripción y precio", Toast.LENGTH_SHORT).show()
return
}
val admin = AdminSQLiteOpenHelper(this)
val db = admin.writableDatabase
val registro = ContentValues().apply {
put("descripcion", descripcion)
put("precio", precio.toDouble())
}
val cant = db.update("articulos", registro, "codigo = ?", arrayOf(codigo))
db.close()
if (cant == 1) {
Toast.makeText(this, "Artículo modificado", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "No existe un artículo con ese código", Toast.LENGTH_SHORT).show()
}
}
private fun limpiarCampos() {
etCodigo.setText("")
etDescripcion.setText("")
etPrecio.setText("")
}
}
Explicación resumida de cada operación¶
1 Alta¶
- Se abre la BD en modo escritura (
writableDatabase). - Se crea un
ContentValuesconcodigo,descripcion,precio. - Se inserta con
insert().
2 Consulta por código¶
- Se consulta por
codigo = ?conselectionArgs. - Si existe, se cargan
descripcionyprecio.
3 Consulta por descripción¶
- Se consulta por
descripcion = ?. - Si existe, se cargan
codigoyprecio.
4 Baja por código¶
- Se elimina con
delete()usandocodigo = ?. - Devuelve cuántas filas se han borrado.
5 Modificación¶
- Se actualiza con
update()usandocodigo = ?. - Se cambian
descripcionyprecio.
Actividad¶
- AC604. Realiza el ejemplo en Compose, realiza una investigación para poder replicarlo.