Saltar a contenido

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:

  1. Alta (carga) de artículo.
  2. Consulta por código.
  3. Consulta por descripción.
  4. Borrado por código.
  5. 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:

  • EditText para 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 ContentValues con codigo, descripcion, precio.
  • Se inserta con insert().

2 Consulta por código

  • Se consulta por codigo = ? con selectionArgs.
  • Si existe, se cargan descripcion y precio.

3 Consulta por descripción

  • Se consulta por descripcion = ?.
  • Si existe, se cargan codigo y precio.

4 Baja por código

  • Se elimina con delete() usando codigo = ?.
  • Devuelve cuántas filas se han borrado.

5 Modificación

  • Se actualiza con update() usando codigo = ?.
  • Se cambian descripcion y precio.

Actividad

  • AC604. Realiza el ejemplo en Compose, realiza una investigación para poder replicarlo.