Configura SQLite para KMP

La biblioteca androidx.sqlite contiene interfaces abstractas junto con implementaciones básicas que se pueden usar para compilar tus propias bibliotecas que acceden a SQLite. Se recomienda que consideres usar la biblioteca de Room, que brinda una capa de abstracción para SQLite que permite acceder a la base de datos sin problemas y, al mismo tiempo, aprovechar toda la potencia de SQLite.

Configura dependencias

Para configurar SQLite en tu proyecto de KMP, agrega las dependencias de los artefactos en el archivo build.gradle.kts de tu módulo:

[versions]
sqlite = "2.7.0"

[libraries]
# The SQLite Driver interfaces
androidx-sqlite = { module = "androidx.sqlite:sqlite", version.ref = "sqlite" }

# The bundled SQLite driver implementation
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }

[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }

APIs de controlador de SQLite

Los grupos de bibliotecas androidx.sqlite ofrecen APIs de bajo nivel para comunicarse con la biblioteca de SQLite, ya sea incluida en la biblioteca cuando se usa androidx.sqlite:sqlite-bundled o en la plataforma host, como Android o iOS, cuando se usa androidx.sqlite:sqlite-framework. Las APIs siguen de cerca la funcionalidad principal de la API de SQLite en C.

Existen tres interfaces principales:

En el siguiente ejemplo, se muestran las APIs principales:

fun main() {
  val databaseConnection = BundledSQLiteDriver().open("todos.db")
  databaseConnection.execSQL(
    "CREATE TABLE IF NOT EXISTS Todo (id INTEGER PRIMARY KEY, content TEXT)"
  )
  databaseConnection.prepare(
    "INSERT OR IGNORE INTO Todo (id, content) VALUES (? ,?)"
  ).use { stmt ->
    stmt.bindInt(index = 1, value = 1)
    stmt.bindText(index = 2, value = "Try Room in the KMP project.")
    stmt.step()
  }
  databaseConnection.prepare("SELECT content FROM Todo").use { stmt ->
    while (stmt.step()) {
      println("Action item: ${stmt.getText(0)}")
    }
  }
  databaseConnection.close()
}

Al igual que con las APIs en C de SQLite, el patrón de uso común consta de estos pasos:

  1. Abrir una conexión de base de datos con la implementación de SQLiteDriver que se creó
  2. Preparar una sentencia de SQL con SQLiteConnection.prepare
  3. Ejecutar un SQLiteStatement de la siguiente manera:
    1. Opcional: Vincular argumentos con las funciones bind*
    2. Iterar el conjunto de resultados con la función step
    3. Leer columnas del conjunto de resultados con las funciones get*

Implementaciones de controladores

En la siguiente tabla, se resumen las implementaciones de controladores disponibles:

Nombre de clase

Artefacto

Plataformas compatibles

AndroidSQLiteDriver androidx.sqlite:sqlite-framework

Android

NativeSQLiteDriver androidx.sqlite:sqlite-framework

iOS, Mac y Linux

BundledSQLiteDriver androidx.sqlite:sqlite-bundled

Android, iOS, Mac, Linux y JVM (computadoras)

WebWorkerSQLiteDriver androidx.sqlite:sqlite-web

JavaScript y WebAssembly (WasmJS)

La implementación recomendada para usar es BundledSQLiteDriver, disponible en androidx.sqlite:sqlite-bundled. Incluye la biblioteca de SQLite compilada desde la fuente, que ofrece la versión más actualizada y coherencia en todas las plataformas de KMP compatibles.

Controlador de SQLite y Room

Las APIs de controlador son útiles para las interacciones de bajo nivel con una base de datos SQLite. Para obtener una biblioteca con muchas funciones que proporcione un acceso más sólido a SQLite, te recomendamos Room.

Una RoomDatabase depende de un SQLiteDriver para realizar operaciones de bases de datos, y debes configurar una implementación con RoomDatabase.Builder.setDriver. Room provides RoomDatabase.useReaderConnection and RoomDatabase.useWriterConnection for more direct access to the managed database connections.

Migra a Kotlin multiplataforma

Debes migrar cualquier uso de componentes de la API de SQLite de Support de bajo nivel, como la interfaz SupportSQLiteDatabase, a los componentes equivalentes del controlador de SQLite.

Kotlin multiplataforma

Realiza una transacción con SQLiteConnection de bajo nivel

val connection: SQLiteConnection = ...
connection.execSQL("BEGIN IMMEDIATE TRANSACTION")
try {
  // perform database operations in transaction
  connection.execSQL("END TRANSACTION")
} catch(t: Throwable) {
  connection.execSQL("ROLLBACK TRANSACTION")
}

Ejecuta una consulta sin resultado

val connection: SQLiteConnection = ...
connection.execSQL("ALTER TABLE ...")

Ejecuta una consulta con resultado, pero sin argumentos

val connection: SQLiteConnection = ...
connection.prepare("SELECT * FROM Pet").use { statement ->
  while (statement.step()) {
    // read columns
    statement.getInt(0)
    statement.getText(1)
  }
}

Ejecuta una consulta con resultado y argumentos

connection.prepare("SELECT * FROM Pet WHERE id = ?").use { statement ->
  statement.bindInt(1, id)
  if (statement.step()) {
    // row found, read columns
  } else {
    // row not found
  }
}

Solo para Android

Realiza una transacción con SupportSQLiteDatabase

val database: SupportSQLiteDatabase = ...
database.beginTransaction()
try {
  // perform database operations in transaction
  database.setTransactionSuccessful()
} finally {
  database.endTransaction()
}

Ejecuta una consulta sin resultado

val database: SupportSQLiteDatabase = ...
database.execSQL("ALTER TABLE ...")

Ejecuta una consulta con resultado, pero sin argumentos

val database: SupportSQLiteDatabase = ...
database.query("SELECT * FROM Pet").use { cursor ->
  while (cursor.moveToNext()) {
    // read columns
    cursor.getInt(0)
    cursor.getString(1)
  }
}

Ejecuta una consulta con resultado y argumentos

database.query("SELECT * FROM Pet WHERE id = ?", id).use { cursor ->
  if (cursor.moveToNext()) {
    // row found, read columns
  } else {
    // row not found
  }
}